zoukankan      html  css  js  c++  java
  • [算法总结]贪心

    贪心算法是每次选举决策时保证当前状况最优(局部最优)策略的算法。因此在使用贪心时需要保证整体最优解可以由局部最优解导出。
    贪心算法的使用通常需要证明,以下为几种常见的证明方法:

    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
    ",num);
    	sort(c+1,c+1+n,cmp);
    	for(int i=1;i<=n;i++)
    		printf("%d
    ",c[i].vis);
    	return 0;
    }
    

    3.P1080 国王游戏

    分析:
    本题的解题关键就在于,如何排序使得得到金币最多的大臣获得的金币数最少。
    我们可以使用邻项交换的方式推导,设两个相邻的大臣(x)(x+1),我们分别计算这两个大臣在交换位置之前和交换位置之后的最大值。得到下列柿子:

    [maxleft(frac{1}{B[i]},frac{A[i]}{B[i+1]} ight)$$ $$maxleft(frac{1}{B[i+1]},frac{A[i+1]}{B[i]} ight) ]

    此时两边同时乘(B[i]*B[i+1]),可得:

    [maxleft(B[i+1],A[i]*B[i] ight) ]

    [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;
    }
    

    pic.png

  • 相关阅读:
    Web 安全 —— XSS攻击的原理
    HTTP缓存
    JavaScript 事件循环机制(Event Loop)
    搭建前端监控系统
    电脑 直接下载cyida deb
    基于样例的传统图像修补算法实现
    Android WorkManager 定时任务
    Android WorkManager工作约束,延迟与查询工作
    微信小程序地图如何显示附近厕所WC步行路线
    物流解决方案再添利器 腾讯位置服务推出货运“三件套”
  • 原文地址:https://www.cnblogs.com/cyanigence-oi/p/11756470.html
Copyright © 2011-2022 走看看