导言
对于二分+贪心算法组合的题,如果最开始不清楚原理的话确实有一些难理解。其实弄懂原理后,很简单。首先先找出想要求的目标的范围(最大值和最小值),然后利用二分法,对中间的数不断进行尝试,看看所测试的答案是否满满足或不满足前提(或者标准),不断逼近正确的答案。这么说还是很抽象,下面通过一些题来具体分析
二分+贪心法实例1:NYOJ 586 疯牛
疯牛
-
描述
-
农夫 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
-
有多组测试数据,以EOF结束。
这道题乍一看很复杂,要是分析清楚还是很容易理解的。这道题我们最终的目的是什么?是为了求出所以可能放牛的方案中,那个方案的最小距离最大。而这个距离,使得每两个牛之间(隔间)的间距只能大于或等于这个距离。首先找出最大距离作为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的最大化
-
描述
-
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
-
有多组测试数据
#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
输出
现在要你求出所有人都能够得到的最大块奶酪的体积是多少?保留四位小数
样例输入
样例输出
提示
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,青蛙过桥,也是一道很好很好的题,我就不贴出代码和题目了