关于二分+贪心的体会

导言

对于二分+贪心算法组合的题,如果最开始不清楚原理的话确实有一些难理解。其实弄懂原理后,很简单。首先先找出想要求的目标的范围(最大值和最小值),然后利用二分法,对中间的数不断进行尝试,看看所测试的答案是否满满足或不满足前提(或者标准),不断逼近正确的答案。这么说还是很抽象,下面通过一些题来具体分析

二分+贪心法实例1:NYOJ 586 疯牛

疯牛

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 4
描述
农夫 John 建造了一座很长的畜栏,它包括N (2 <= N <= 100,000)个隔间,这些小隔间依次编号为x1,...,xN (0 <= xi <= 1,000,000,000).
但是,John的C (2 <= C <= N)头牛们并不喜欢这种布局,而且几头牛放在一个隔间里,他们就要发生争斗。为了不让牛互相伤害。John决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能的大,那么,这个最大的最小距离是什么呢?
输入
有多组测试数据,以EOF结束。
第一行:空格分隔的两个整数N和C
第二行——第N+1行:分别指出了xi的位置
输出
每组测试数据输出一个整数,满足题意的最大的最小值,注意换行。
样例输入
5 3
1
2
8
4
9
样例输出
3

这道题乍一看很复杂,要是分析清楚还是很容易理解的。这道题我们最终的目的是什么?是为了求出所以可能放牛的方案中,那个方案的最小距离最大。而这个距离,使得每两个牛之间(隔间)的间距只能大于或等于这个距离。首先找出最大距离作为right(隔间编号可以代表距离,那么right就是编号最大的房间)及left(编号最小的房间,或者0也行),而后测试中间数mid。放牛的方法是如果该隔间的距离与上个放有牛的隔间的距离大于或等于mid,那么该隔间可以放牛。以此类推至所有隔间结束,然后判断所放进去的牛是否大于或等于牛的实际个数,如果true,那么mid或许可以更加大一些,则将left=mid+1,再次利用二分法测是下一个mid。反之如果是false,则说明mid太大了,需要测试一个小一些的数,那么将right=mid-1再测试即可。

下面是我的代码

#include<iostream>
#include<algorithm>
using namespace std;
int rooms[100000];
int n,c;

int judge(int maxl)
{
	int length=rooms[0];
	int temp=1;
	for(int i=0;i<n;i++)
	{
		if(rooms[i]-length>=maxl)
		{
			temp++;
			length=rooms[i];
		}
	}
	return temp;
}

int main()
{
	while(cin>>n>>c)
	{
		for(int i=0;i<n;i++)
			cin>>rooms[i];
		sort(rooms,rooms+n);
		int left=0,right=rooms[n-1];
		while(left<=right)
		{
			int mid=(left+right)/2;
			if(judge(mid)<c)
				right=mid-1;
			else
				left=mid+1; 
		}
		cout<<left-1<<endl;
	}
	return 0;
}        

下面是另一道题,跟这道题原理是相似的,但有些不一样

二分+贪心实例2:NYOJ 914

Yougth的最大化

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 4
描述

Yougth现在有n个物品的重量和价值分别是Wi和Vi,你能帮他从中选出k个物品使得单位重量的价值最大吗?

输入
有多组测试数据
每组测试数据第一行有两个数n和k,接下来一行有n个数Wi和Vi。
(1<=k=n<=10000) (1<=Wi,Vi<=1000000)
输出
输出使得单位价值的最大值。(保留两位小数)
样例输入
3 2
2 2
5 3
2 1
样例输出
0.75


这道题我就不详细说明了,也是用二分加贪心。我刚见了这道题第一眼还以为是01背包呢。但判断结束的条件不一样,上一道是整数,判断二分结束的条件是left是否大于等于right,而这道题则是小数了,所以条件得换成right-left是否小于一个特别小的数,比如0.00001,1e-4啥的。
下面是贴代码
 
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxsize=10005;
double w[maxsize],v[maxsize],sub[maxsize];
int n,k;

bool cmp(double a,double b)
{
	return a>b;
}

bool judge(double mid)
{
	int i;
	for(i=0;i<n;i++)
		sub[i]=v[i]-w[i]*mid;
	sort(sub,sub+n,cmp);
	double sum=0;
	for(i=0;i<k;i++)
		sum+=sub[i];
	if(sum>=0)	
		return true;
	return false; 
}

int main()
{
	int i;
	while(scanf("%d%d",&n,&k)!=EOF)
	{
		double maxavg=0;
		for(i=0;i<n;i++)
		{
			scanf("%lf%lf",&w[i],&v[i]);
			maxavg=max(maxavg,v[i]/w[i]);
		}
		double l=0;
		double r=maxavg;
		double mid;
		while(r-l>1e-5)
		{
			mid=(r+l)/2;
			if(judge(mid))	
				l=mid;
			else 
				r=mid;
		}
		printf("%.2lf\n",l);
	}
	return 0;
}        
然后还有一道题,又是这些题的升级版,感觉把二分贪心用的淋漓尽致

二分+贪心实例3:题目Pie

题目描述

题意:作者要开一个生日party,他现在拥有n块高度都为1的圆柱形奶酪,已知每块奶酪的底面半径为r不等,作者邀请了f个朋友参加了他的party,他要把这些奶酪平均分给所有的朋友和他自己(f+1人),每个人分得奶酪的体积必须相等(这个值是确定的),形状就没有要求。现在要你求出所有人都能够得到的最大块奶酪的体积是多少?

要求是分出来的每一份必须出自同一个pie,也就是说当pie大小为3,2,1,只能分出两个大小为2的,剩下两个要扔掉。

输入

T组测试数据 1<=T<=100

n,f   n块高度都为1的圆柱形奶酪,f个人 1 ≤ n,f ≤ 10 000

每块奶酪的底面半径为r, 1 ≤ ri ≤ 10 000

输出

现在要你求出所有人都能够得到的最大块奶酪的体积是多少?保留四位小数

样例输入

33 34 3 3 1 24510 51 4 2 3 4 5 6 5 4 2

样例输出

25.13273.141650.2655

提示

PI = 3.1415926535897932;


要小心的地方就是蛋糕不够大了,分不了一个人了,就要扔掉。
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
#define PI 3.1415926535897932
double v[10000];
int n,f;
bool judge(double mid)
{
	double s[10000];
	for(int i=0;i<n;i++)
	{
		s[i]=v[i];
	}
	int temp=0;
	int i;
	for(i=0;i<n;i++)          //其实可以不用循环,有更好的方法,就是相除取整
	{
		while(s[i]>=mid)
		{
			s[i]-=mid;
			temp++;
			if(temp>=f + 1)
				return true;
		}
	}
	return false;
}

int main()
{
	int N;
	int i;
	cin>>N;
	while(N--)
	{
		double a;
		cin>>n>>f;
		for(i=0;i<n;i++)
		{
			scanf("%lf",&a);
			v[i]=a*a*PI;
		}
		double l=0;
		double r=v[n-1];
		double mid;
		while(r-l>=1e-5)
		{
			mid=(l+r)/2.0;
			if(judge(mid))
				l=mid;
			else
				r=mid;
		}
		printf("%.4lf\n",r);
	}
	return 0;
 } 
就是这么多。此外NYOJ的619,青蛙过桥,也是一道很好很好的题,我就不贴出代码和题目了

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值