[算法总结]贪心

[TOC]

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

pic.png

posted @ 2019-10-29 00:19  Wolfloral  阅读(378)  评论(0编辑  收藏  举报