贪心专题

概念:

通过正确的贪心策略来实现程序的最优解。

常用方法:

1.微扰

2.范围缩放

3.决策包容性

4.反证法

5.数学归纳法

各类例题:

一.智力大冲浪

大致题意:刚开始有m元钱,有n个小游戏,且每个小游戏有一个对应的时间段,每没有完成一个小游戏会扣去相应的钱数。求最多能获得多少钱。其中 ∑ 扣去的钱数<m.

做法:将每个游戏的时间段和钱数用结构体存储,以扣去的钱数为第一关键字进行从大到小排序; 把i从1-n进行枚举,再用j从time[i]到1进行遍历,若当前的时间没有被占用,则将该时间标记。若time[i]到1的时间全部被占用,则在总钱数中扣去对应的钱数即可。

证明:对于一个游戏,我们当然希望它玩得越迟越好,这样既可以少占用别的游戏所需要的时间,又可以尽可能少的捡钱。

主要代码如下:

inline void work() {
	for(int i=1;i<=n;i++) {
		int j;
		for(j=a[i].time;j;j--) 
			if(!vis[j]) {
				vis[j]=1;
				break;
			}
		if(!j) m-=a[i].money;
	} 
}

二.纪念品分组(NOIP2007)

大致题意:共有n个纪念品,每个纪念品有一个价值,可以将多个纪念品分为一组,每组的价值不能超过w,求最少的分组数。

做法:将n个纪念品的价值从小到大进行排序,同时枚举左右端点,若左端点的价格+右端点的价格已经超过w,则将右端点左移,同时将分组数+1.若左右端点和<=w,则分别将左右端点向中移,分组数+1.

证明:当当前区间内价值最小的纪念品与价值最大的纪念品的和超过w时,说明那个价值最大的纪念品和任何纪念品分都不可能不超过w,所以将它自成一组;不超过w时,枚举下一组即可。

主要代码如下:

int i=1;j=m;
while(i<=j)
{
	if(a[i]+a[j]<=n) i++,j--,sum++;
        else j--,sum++;
}

三.三值排序(USACO)

大致题意:有n个数的序列,这n个数只由1、2、3组成,可以将序列中的任意两个数进行交换,求将这个序列排成升序所需的最少次数。

做法:先从应该是2的位置开始枚举,每有一个1,就在1的区域去寻找2,找到就交换,并把次数+1;若没有2,则与3交换,将次数+1.再从应该是3的位置开始枚举,每有一个1,就在1的区域去寻找3,找到就交换,并把次数+1;若没有3,则与2交换,将次数+1.最后将错位的2、3归位。

证明:为了保证做法正确,需要先将1全部归位,而在将1归位的同时,也要先将被交换数尽可能的交换到要求的区间中去,若交换数的区间中已不符合要求,再将另一数进行交换。

部分代码如下:

inline void work() { //one,two,three分别指1、2、3的个数 
    for(int i=one+1;i<=one+two;i++) 
    	if(a[i]==1) {
    		bool flag=0;
			for(int j=1;j<=one;j++) 
				if(a[j]==2) {
					swap(a[i],a[j]);
					sum++;
					flag=1;
					break;
				} 
			if(!flag)  
				for(int j=1;j<=one;j++) 
					if(a[j]==3) {
						swap(a[i],a[j]);
						sum++;
						break;
					}
		}
    for(int i=one+two+1;i<=one+two+three;i++)
		if(a[i]==1) { 
			bool flag=0;
			for(int j=1;j<=one;j++)
				if(a[j]==3) {
					swap(a[i],a[j]);
					sum++;
					flag=1;
					break;
				}
			if(!flag) 	
				for(int j=1;j<=one;j++) 
					if(a[j]==2) {
						swap(a[i],a[j]);
						sum++;
						break;
					} 
		}
		for(int i=one+1;i<=one+two;i++) 
			if(a[i]==3) sum++;
}

我们按照如上做法,可以将代码简化。

做法:找出不正确的1、2、3,将不正确的1的个数与2的位置中的3与3的位置中的2的最大值相加。

代码如下:

#include<bits/stdc++.h>
using namespace std;

int n,x=0,y=0,z=0,sum=0,t1=0,t2=0;
int a[1001];

inline int read() {
	int s=0;char c=getchar();
	while(c>='0' && c<='9') s=s*10+c-48,c=getchar();
	return s;
}

inline void init() {
	n=read();
	for(int i=1;i<=n;i++) {
		a[i]=read();
		if(a[i]==1) x++;
		if(a[i]==2) y++;
		if(a[i]==3) z++; 
	}
}

inline void work() {
	for(int i=1;i<=x;i++) if(a[i]!=1) sum++; 
	for(int i=x+1;i<=x+y;i++) if(a[i]==3) t1++;
	for(int i=x+y+1;i<=x+y+z;i++) if(a[i]==2) t2++; 
}

inline void outo() {
	printf("%d\n",sum+max(t1,t2));
}

int main() {
	init();
	work();
	outo();
	return 0;
} 

四.修理牛棚(USACO)

大致题意:共有S个牛棚,只有C个牛棚中有牛,需要将C个有牛的牛棚的顶补好,使用的模板数不能超过M,求这些模板的最小长度和。

做法:刚开始用一块木板,长度和为S,随后将两端没有牛的牛棚数减去,再从没有标记中的区域寻找最大的没有被标记的空位,将它减去,直到达到了最大的木板数。

证明:将空位最大到第m大的牛棚数减去,就保证了最小的长度和。(我感觉这很容易理解就不多解释了)

代码如下:

#include<bits/stdc++.h>
using namespace std;

int M,S,C,sum;
int a[201];

inline int read() {
	int s=0;char c=getchar();
	while(c>='0' && c<='9') s=s*10+c-48,c=getchar();
	return s;
}

inline void init() {
	M=read(),S=read(),C=read(); 
	sum=S;
	for(int i=1;i<=C;i++) a[i]=read();
	sort(a+1,a+C+1); 
} 

inline bool cmp(int x,int y) {
	return x>y;
} 

int b[201];
inline void work() {
	sum-=(a[1]-1);
	sum-=(S-a[C]);
	for(int i=1;i<C;i++) b[i]=a[i+1]-a[i]-1;
	sort(b+1,b+C,cmp);
	for(int i=1;i<M;i++) sum-=b[i];
}

inline void outo() {
	printf("%d\n",sum);
}

int main() {
	init();
	work();
	outo();
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/chenkainan1023/article/details/80883614