贪心总结

版权声明:本文为博主原创文章,未经博主允许必须转载。 https://blog.csdn.net/C20181220_xiang_m_y/article/details/75106242
	好久没写博客啦
	最近做了许多贪心的题目,感觉整个人的身心都通透了啊
	做着做着,发现其实贪心也是有许多基本模型的,趁热打铁,把孤的心得体会记录下来,希望对大家,也对孤有所帮助。
	(以下内容为孤自己整理,有不足之处还请多多指教)
一、区间覆盖问题
	常常会遇到这样一种题目:在一条(时间、数等)轴上,有几段区间。通常会有这几种问法:
	(1)、在轴上放最少的点,使得每一段区间都至少有一(若干)个点。
	例:POJ 1328 Radar Installation
	题目大意是在X轴上方有几个小岛,现在要在X轴放雷达,已知雷达覆盖半径与小岛位置,求最少放多少雷达使得每个小岛都被覆盖。
	题解:由半径和小岛的y值,据勾股定理可求出每个小岛都在X轴上有一段对应区间,在此区间内可以覆盖小岛,于是问题便转化为了:在轴上放最少的点,使得每一段区间都至少有一个点。
	对于此类题目,通常区间按照右端点从小到大排序,再在没有点的区间的最右边放上一个点。
	为什么呢?如何保证这样是最优的?
	想象一下,在你面前有几段区间,先看第一段区间,其它区间的右端点都在它的右端点的右边,所以,在它的右端点上放上一个点,能够在保证此区间被覆盖的前提下,尽可能多地覆盖后面的区间,对于当前这个区间,无论后面如何,这种选法都是最优的,并且不会给后面的选择造成坏的影响。
上代码:

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
int t[2],ans;
struct node
{
	double x,y;
	bool operator < (const node &p)const{return y<p.y;}
}a[1005];
int main()
{
	int n,d,tot=0;
	bool flag;
	double o;
	while(scanf("%d%d",&n,&d),n||d)
	{
		printf("Case %d: ",++tot);
		flag=0;
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d",&t[0],&t[1]);
			if(t[1]>d||flag) {flag=1;continue;}
			a[i].x=t[0]-sqrt(d*d-t[1]*t[1]);
			a[i].y=t[0]+t[0]-a[i].x;
		}
		if(flag) {printf("-1\n");continue;}
		sort(a+1,a+1+n);
		for(int i=1;i<=n;)
		{
			ans++;
			o=a[i].y;
			while(a[i].x<=o&&i<=n) i++;
		}
		printf("%d\n",ans);
		ans=0;
	}
}

ps:如果你想通了,那么肯定也能想通,按照左端点由大到小排,放在左端点也是可以的。  
(2)、各个区间重叠排斥,每个单位时间只能完成不排斥的区间,最少需要多少时间?	
例1:POJ 1083 Moving Tables	
题目大意是要从i教室搬桌子到j教室,而走廊的宽度只够一张桌子移动,问需要多少分钟才能搬完所有桌子?
	题解:首先注意由于走廊是两侧,所以当起点是偶数时,会将起点的前一个点也占用,当终点是奇数时,会将终点的后一个点也占用。
	对于这类题,先画图分析:
看以上这5个区间,有的互相排斥,有的却可以同时完成,如红线所示,重叠最多的点是4,重叠了4个区间。
	实际上,答案就是重叠最多的点所重叠的次数
证明:(数学归纳法)
	假设当最多有n个桌子重叠时,搬n趟可以搬完。
	那么当最多有n+1个桌子重叠时,先忽略第n+1层桌子,前n趟可以将有n次及以下重叠的桌子搬完,此时仅剩下第n+1层最多仅有一个桌子重叠,此时再搬一趟,就可以全部搬完。
	所以,当n=k成立时,n=k+1也成立。
	因为n=1时显然成立,所以对于所有正整数,假设成立,证毕。
上代码:
#include<cstring>
int main()
{
	int tmp,n,x,y,a[405];
	scanf("%d",&tmp);
	for(int u=1;u<=tmp;u++)
	{
		memset(a,0,sizeof a);
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d",&x,&y);
			if(x>y) x^=y,y^=x,x^=y;//交换
			if(x%2==0) a[x-1]++;
			if(y%2) a[y+1]++;
			for(int j=x;j<=y;j++) a[j]++;//标记覆盖
		}
		x=0;
		for(int i=1;i<=400;i++)
			if(a[i]>x) x=a[i];
		printf("%d\n",x*10);
	}
}




例2: POJ 3190 Stall Reservations
	与Moving Tables类似,只是需要输出方案。这里使用第二种解法。
	题解:将区间按左端点从小到大排序,依次放入,能放则放,不能放则再加一个牛棚。
	证明:暂且不管前面如何防置,我们来看已经放好一些区间时的情况:
此时倘若接下来起点为6的区间和一个起点为7的区间,你先放哪个?(注意已经按照左端点排过序,此时6和7是最前面的两个数)
果断选6啊,因为无论如何最后6和7都是要放入牛棚的,如果此时放7,则浪费了更多的空间
而且基于此原理,显然,6应该放入2号牛棚
不知你有没有一种感觉,其实此类贪心,就是在不产生损害的前提下,将产生有利情况的可能性最大化。
上代码:
#include<algorithm>
#include<queue>
using namespace std;
struct node
{
	int l,r,id;
	bool operator < (const node& p)const{return r>p.r;}//越早结束越优先
}a[50005];
priority_queue<node>q;
bool cmp(node a,node b)
{
	return a.l<b.l;
}
int main()
{
	int n,b[50005],ans=1;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&a[i].l,&a[i].r);
		a[i].id=i;
	}
	sort(a+1,a+1+n,cmp);
	b[a[1].id]=ans;
	q.push(a[1]);
	for(int i=2;i<=n;i++)
	{
		if(q.top().r<a[i].l)//尽可能往前放
		{
			b[a[i].id]=b[q.top().id];
			q.pop();
		}
		else//如果最早结束的都无法放下的话,就得加一个牛棚了
			b[a[i].id]=++ans;
		q.push(a[i]);
	}
	printf("%d\n",ans);
	for(int i=1;i<=n;i++)
		printf("%d\n",b[i]);
}


二、合并果子问题
	相信大家都对这种问题很熟悉吧~
	例1:POJ 3253 Fence Repair 
		例2:POJ 1862 Stripies
	这种类型的题,一般是给一种计算规则,然后计算最大或最小,通常我们会根据这种计算规则的增减性来决定优先级。通常这种题有两种出法,一种是合并,一种是分割,这两者并无异处,可以一视同仁。
	如例一,就是合并果子的分割版本,它的计算规则是加法,会使值增大,而通过简单地举例或数学式子可以发现,越早合并的值经历计算规则的次数会越多。题目要求最小值,只需让最小的值去通过计算规则就好了。
	又如例二,给出了另外的计算规则:2*sqrt(m1*m2),此时开根号会使值变小,题目要求最小值,只需让最大的值去通过计算规则就好了。
上代码:
例一:
#include<cstdio>
#include<queue>
using namespace std;
priority_queue<long long,vector<long long>,greater<long long> >a;
int main()
{
	int n;
	long long x,y,s=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&x),a.push(x);
	for(int i=1;i<n;i++)
	{
		x=a.top(),a.pop();
		y=a.top(),a.pop();
		s+=x+y;a.push(x+y);
	}
	printf("%lld",s);
}


例二:
#include<cstdio>
#include<queue>
using namespace std;
priority_queue<long long,vector<long long>,greater<long long> >a;//greater是小根堆,最小值优先,less相反
int main()
{
	int n;
	long long x,y,s=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&x),a.push(x);
	for(int i=1;i<n;i++)
	{
		x=a.top(),a.pop();
		y=a.top(),a.pop();
		s+=x+y;a.push(x+y);
	}
	printf("%lld",s);
}


三、找决定优先级的因素
	这类题,一般乍一看让人很摸不着头脑,无从下手。
	例:POJ 3262 Protecting the Flowers
	要是时间多的吃得多,时间少的吃得少,该怎么办呢???
	先从简单情况分析:
	假设有两头牛A,B。
若选A,则损失timeA * eatB
若选B,则损失timeB * eatA
如何比较?同时乘以1/(eatA*eatB)。那么损失的大小关系就是timeA/eatA和timeB/eatB的大小关系!
同理,用数学归纳法,可得优先级就是由时间与吃的比例来确定的。但是这里孤要介绍另一种方法:交换比较法
众所周知,贪心一般都有个先后顺序,不同的顺序会导致不同的结果,那么,我们任选一种选法,交换其中两项的顺序,再与原来比较,再找出现有顺序与原有顺序的区别,就可以得到决定结果的顺序因素!
以这道题举个例子:设牛的时间为Ti,吃草速度为Di,那么:
先牵走牛1,损失:(sumD-D1)*2*T1
再牵走牛2,损失:(sumD-D1-D2)*2*T2
再牵走牛3,损失:(sumD-D1-D2-D3)*2*T3
......以此类推
我们将牵走牛1与牛2的顺序交换,再看:
先牵走牛2,损失:(sumD-D2)*2*T2
再牵走牛1,损失:(sumD-D1-D2)*2*T1
再牵走牛3,损失:(sumD-D1-D2)*2*T3
......此后同上
可以看出,两种顺序的总损失是不一样的。从牛3往后相同,所以我们要比较前两头牛。
第一种选法,损失:sumD*(2*T1+2*T2)-2*D1*T1-2*D1*T2-2*D2*T2
第二种选法,损失:sumD*(2*T1+2*T2)-2*D1*T1-2*D2*T1-2*D2*T2
差别就在红色字处。倘若我们要第一种选法的损失小于第二种选法,则要D1*T2<D2*T1
即D1/T1<D2/T2   !!!
所以,决定优先级的因素被我们找出来了。
这样,交换其中两个元素的顺序进行对比的方法,可以探究隐藏的规律。同样的对比方法,在分治的三分法中也有体现。
	

	呼~,好久没有如此舒畅地写过博客了,爽!


猜你喜欢

转载自blog.csdn.net/C20181220_xiang_m_y/article/details/75106242