目录
贪心算法是每次选举决策时保证当前状况最优(局部最优)策略的算法。因此在使用贪心时需要保证整体最优解可以由局部最优解导出。
贪心算法的使用通常需要证明,以下为几种常见的证明方法:
1. 邻项交换:
在任意局面下,任何元素位置的改变都会影响当前局面的改变。该方法NOIP曾有涉及。
对局面元素的排序可以为贪心策略提供证明。
2. 范围缩放:
扩大局部最优解的范围不会影响整体最优的情况。
3. 反证法,归纳法:
如同字面意思。 (=´ω`=)
例题:
1.P2887 [USACO07NOV]防晒霜Sunscreen
分析:
把每个奶牛按照minSPF递减排序,并在防晒霜中找SPF最大的给这头奶牛用(在合理范围内)。假如有两瓶防晒霜x,y可用,其中spf[x]>spf[y]。那么对于下一头奶牛来说,有x,y都能用,只能用y,和x,y都不能用这三种情况。显然,我们将spf低的防晒霜给后面的奶牛用会更优。
代码如下:
#include<bits/stdc++.h> #define N 5000 using namespace std; struct node{ int l,r; bool operator <(const node &other)const{ return l>other.l; } }c[N]; struct temp{ int num,power; bool operator <(const temp &other)const{ return power>other.power; } }s[N]; int n,m,ans; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d%d",&c[i].l,&c[i].r); for(int i=1;i<=m;i++) scanf("%d%d",&s[i].power,&s[i].num); sort(c+1,c+n+1); sort(s+1,s+1+m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ if(s[j].power>=c[i].l&&s[j].power<=c[i].r&&s[j].num){ s[j].num--;ans+=1;break; } } printf("%d",ans); return 0; }
2.P2859 [USACO06FEB]摊位预订Stall Reservations
分析:
按照奶牛们开始挤奶的的时间排序,如果与下一个牛的挤奶时间与上一头牛的时间重合,那么新建一个牛棚。
代码如下:
#include<bits/stdc++.h> #define N 50010 using namespace std; int n,num=1; struct node{ int l,r,id,vis; bool operator <(const node &other)const{ return l<other.l; } }c[N]; inline int cmp(node x,node y){ return x.id<y.id; } priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q; int main() { scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d%d",&c[i].l,&c[i].r); c[i].id=i; } sort(c+1,c+n+1); q.push(make_pair(c[1].r,1)); c[1].vis=1; for(int i=2;i<=n;i++){ int stop=q.top().first; int snum=q.top().second; if(c[i].l>stop){ q.pop();q.push(make_pair(c[i].r,snum)); c[i].vis=snum; } else{ num++; c[i].vis=num; q.push(make_pair(c[i].r,num)); } } printf("%d\n",num); sort(c+1,c+1+n,cmp); for(int i=1;i<=n;i++) printf("%d\n",c[i].vis); return 0; }
3.P1080 国王游戏
分析:
本题的解题关键就在于,如何排序使得得到金币最多的大臣获得的金币数最少。
我们可以使用邻项交换的方式推导,设两个相邻的大臣\(x\),\(x+1\),我们分别计算这两个大臣在交换位置之前和交换位置之后的最大值。得到下列柿子:
\[max\left(\frac{1}{B[i]},\frac{A[i]}{B[i+1]}\right)\] \[max\left(\frac{1}{B[i+1]},\frac{A[i+1]}{B[i]}\right)\]
此时两边同时乘\(B[i]*B[i+1]\),可得:
\[max\left(B[i+1],A[i]*B[i]\right)\]
\[max(B[i],A[i+1]*B[i+1])\]
比较可得,当\(A[i]*B[i]\leq A[i+1]*B[i+1]\)时,序列更优。
代码如下:(高精度,不写只有60分)
#include<bits/stdc++.h> #pragma GCC O(2) #define N 10100 #define INF 0x7f7f7f7f #define ll long long using namespace std; int n,L,R; int ans[N<<1],sum[N<<1],add[N<<1]; struct node{ int l,r; ll key; bool friend operator < (node x,node y){ return x.key<y.key; } }p[N]; inline void modify(){ for(int i=add[0];i>=0;i--) sum[i]=add[i]; } inline int compare(){ if(sum[0]>add[0]) return 0; if(sum[0]<add[0]) return 1; for(int i=add[0];i>=1;i--){ if(add[i]>sum[i]) return 1; if(add[i]<sum[i]) return 0; } } inline void multiply(int num){ memset(add,0,sizeof(add)); for(int i=1;i<=ans[0];i++){ ans[i]=ans[i]*num; add[i+1]+=ans[i]/10; ans[i]%=10; } for(int i=1;i<=ans[0]+5;i++){ ans[i]+=add[i]; if(ans[i]>=10){ add[i+1]+=ans[i]/10; ans[i]%=10; } if(ans[i]) ans[0]=max(ans[0],i); } } inline void divid(int num){ memset(add,0,sizeof(add)); int ext=0; for(int i=ans[0];i>=1;i--){ ext*=10; ext+=ans[i]; add[i]=ext/num; if(add[0]==0&&add[i]) add[0]=i; ext%=num; } } int main() { scanf("%d",&n); for(int i=0;i<=n;i++){ scanf("%d%d",&p[i].l,&p[i].r); p[i].key=p[i].l*p[i].r; } sort(p+1,p+n+1); ans[0]=ans[1]=1; for(int i=1;i<=n;i++){ multiply(p[i-1].l); divid(p[i].r); if(compare()) modify(); } for(int i=sum[0];i>=1;i--) printf("%d",sum[i]); return 0; }
4.P1417 烹调方案
分析:
这到题和上一道题本质是一样的,都是用邻项交换作证明的,只不过这道题的推导简单一些。本题若除去b数组,那么就属于经典的线性Dp问题。想要解决元素之间的先后顺序,不妨设两个食材为\(x,y\),因此代入公式\((ai-t×bi)\)可得:
其中\(p\)为先前做完食材的时间。
\[a[x]-(p+c[x])*b[x]+a[y]-(p+c[x]+c[y])*b[y]\]
\[a[y]-(p+c[y])*b[y]+a[x]-(p+c[y]+c[x])*b[x]\]
化简后易得:\[c[x]*b[y]<c[y]*b[x]\]
因此只要按照这个优先级对食材排序,再进行排序就可以保证答案的正确性。
代码如下:
#include<bits/stdc++.h> #define N 100010 #define int long long using namespace std; struct node{ int a,b,c; }p[N]; inline int cmp(node x,node y){ return x.c*y.b<y.c*x.b; } int f[N],t,n; signed main() { scanf("%d%d",&t,&n); for(int i=1;i<=n;i++) scanf("%d",&p[i].a); for(int i=1;i<=n;i++) scanf("%d",&p[i].b); for(int i=1;i<=n;i++) scanf("%d",&p[i].c); sort(p+1,p+n+1,cmp); for(int i=1;i<=n;i++) for(int j=t;j>=p[i].c;j--){ f[j]=max(f[j],f[j-p[i].c]+p[i].a-j*p[i].b); } int ans=0; for(int i=1;i<=t;i++) ans=max(ans,f[i]); printf("%d",ans); return 0; }