众所不周知,贪心就是一种每次都抢局部最优解的算法(这样的确很贪),但是想要证明贪心的正确性很难。但在比赛中,尤其是不会写DP的你,可以选择大胆尝试,写一个贪心,万一对了呢 (:
本期博客将介绍两种贪心的方法:优先队列回撤贪心&相邻交换。
Part 1:优先队列回撤贪心
我们还是看题吧。
P4053:
此题一(亿)看就只是贪心,但是不会。这里有两个参数:修理建筑的时间和报废的时间。该怎么权衡这两个呢?大家可以先自己想一会。
有思路了吗?其实,没有你想的辣么难,我们可以按报废的时间排序,然后一个优先队列维护,每次加入一个建筑,只要发现它报废了,就把当前堆里最小的那个(也是top)踢出去就可以了。而这,就叫优先队列回撤贪心。怎么样,不难吧。然后放个代码。
#include <bits/stdc++.h>
using namespace std;
struct building{
int t1;
int t2;
}buildings[150005];
bool cmp(building a,building b){
return a.t2<b.t2;
}
priority_queue<int> pq;
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>buildings[i].t1>>buildings[i].t2;
sort(buildings+1,buildings+n+1,cmp);
int sum=0,ans=0;
for(int i=1;i<=n;i++){
sum+=buildings[i].t1;
pq.push(buildings[i].t1);//入队
if(sum<=buildings[i].t2)
ans++;
else{//把堆顶踢出去
sum-=pq.top();
pq.pop();
}
}
cout<<ans<<endl;
return 0;
}
注意要开long long。
P3545:
思路和上一题大差不差。直接看代码吧,有注释。
#include <bits/stdc++.h>
using namespace std;
int a[250005],b[250005];
struct costumer{
int req;
int idx;
};
bool operator < (const costumer& x,const costumer& y){
return x.req<y.req;
}
priority_queue<costumer> pq;//当前满足的顾客的堆,堆顶是需求最大的客户
int ans[250005];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=1;i<=n;i++)
cin>>b[i];
long long ad=0,goods=0;//ad是总需求,goods是总进货数
for(int i=1;i<=n;i++){
goods+=a[i];//进货
pq.push((costumer){b[i],i});
ad+=b[i];//尝试满足第i个人的需求
if(ad>goods){//不够
ad-=pq.top().req;
pq.pop();
}
}
int tot=0;
while(!pq.empty()){
ans[++tot]=pq.top().idx;
pq.pop();
}
cout<<tot<<endl;
for(int i=1;i<=tot;i++)
cout<<ans[i]<<' ';
return 0;
}
十年OI一场空,不开long long见祖宗!
Part 2:相邻交换法
到这趴就得要亿点点数学了。老规矩,看题。
AcWing 58:
难得一道AcWing的题目。
我当时:乍一看,简单!再一看,诶,有点......难...... 再想一会,我根本不会......
言归正传,首先肯定是string输入输出滴,然后就是去排序。怎么个排法呢?
结论:如果 ab > ba,那么就交换a,b在数组内的位置! But, WHY?
很多人应该都不理解(我一开始也不懂),但就是猜对了,很神奇。
首先,直接比较大小肯定不行。那怎么确定谁在左边呢?我们用位置原理展开一下。
假设和是两个要比较的数。用位置原理得到这个鬼玩意:,B同理。然后我们把A在左和B在左分别表示出来,用小于号连接,变成一个很神奇很长打的我都不想再打的一坨式子:
然后开始抵消,A往左边放,B往右边放,就成了这样:继续化简:。好了,结束了,满足传递性,排序一遍就万事大吉了!!!!!!!!!!!
#include <bits/stdc++.h>
using namespace std;
string num[10005];
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++)
cin>>num[i];
sort(num,num+n,[](string x,string y){return x+y<y+x;});
string ans="";
for(string x:num)
ans+=x;
cout<<ans<<endl;
return 0;
}
代码超简单。
P1080:
最后一题了。这题还是很有难度的(毕竟是提高组的)。首先,我们知道你交换两个大臣(x和y),其他大臣的奖金是不变的,只会影响它们两个本身。其次,这会出现四个值(xy顺序有两种,每个顺序还要分别考虑x和y的奖金),所以答案就变成了(A是xy中的x,B是xy中的y,C是yx中的y,D是yx中的x)不太好处理了。没关系,把ABCD分别写出来,会发现AD同分母且A<D;BC同分母,且B>C。所以,B必然小于D因为max(a,b)<max(c,d),然后就找到了排序方法:
bool cmp(minister x,minister y){
return x.l*x.r<y.l*y.r;
}
接下来就完事大吉了,注意需要高精(Python在狂笑)。
#include <bits/stdc++.h>
using namespace std;
struct minister{
int l;
int r;
}ministers[1005];
int product[10005],ans[10005],tmp[10005];
bool cmp(minister x,minister y){
return x.l*x.r<y.l*y.r;
}
void copy(int x[],int y[]){
for(int i=0;i<10005;i++)
x[i]=y[i];
}
bool compare(int x[],int y[]){
for(int i=10004;i>=0;i--){
if(x[i]>y[i])
return true;
if(x[i]<y[i])
return false;
}
return false;
}
void multiply(int multipler[],int num){
for(int i=10003;i>=0;i--)
multipler[i]*=num;
for(int i=0;i<10004;i++){
multipler[i+1]+=(multipler[i]/10);
multipler[i]%=10;
}
}
void divide(int dividend[],int res[],int num){
memset(res,0,sizeof(res));
int x=0;
for(int i=10004;i>=0;i--){
x=x*10+dividend[i];
res[i]=x/num;
x%=num;
}
}
int main(){
int n;
cin>>n;
for(int i=0;i<=n;i++)
cin>>ministers[i].l>>ministers[i].r;
sort(ministers+1,ministers+n+1,cmp);
product[0]=1;
for(int i=0;i<=n;i++){
divide(product,tmp,ministers[i].r);
if(compare(tmp,ans))
copy(ans,tmp);
multiply(product,ministers[i].l);
}
bool flag=false;
for(int i=10004;i>=0;i--){
if(!flag){
if(ans[i])
flag=true;
else
continue;
}
cout<<ans[i]<<' ';
}
return 0;
}
施工完毕。
温馨提示:本期的代码都直接提交均无法AC,请不要无脑Ctrl C+Ctrl V