導言
對於二分+貪心算法組合的題,如果最開始不清楚原理的話確實有一些難理解。其實弄懂原理後,很簡單。首先先找出想要求的目標的範圍(最大值和最小值),然後利用二分法,對中間的數不斷進行嘗試,看看所測試的答案是否滿滿足或不滿足前提(或者標準),不斷逼近正確的答案。這麼說還是很抽象,下面通過一些題來具體分析
二分+貪心法實例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
這道題乍一看很複雜,要是分析清楚還是很容易理解的。這道題我們最終的目的是什麼?是爲了求出所以可能放牛的方案中,那個方案的最小距離最大。而這個距離,使得每兩個牛之間(隔間)的間距只能大於或等於這個距離。首先找出最大距離作爲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,青蛙過橋,也是一道很好很好的題,我就不貼出代碼和題目了