題目鏈接
洛谷原題(除了多組數據都是相同的)鏈接:
解題思路
二分答案。
什么?什么是二分答案?我沒聽過
不要緊,希望這篇文章能幫助不會二分答案的你更好地理解二分的思想。
(神犇求放過)
不扯了,談正題。
大家都做過一個單調遞增函數找零點的題吧,比如這個
還記得怎么做么?什么,沒記得做過這個題?那請您先去做一遍。
先回到最初的起點。
在我們初入程設的時候,循環語句就成為陪伴我們的夢魘朋友。就拿這個題舉例子吧,我可以寫一個這樣的程序來做這道題:
#include<stdio.h>
#include<math.h>
#define pi acos(-1)//π的常用定義方法,建議記住
double f(double x,double y){
return (sin(sqrt(x))+exp(-pow(x,1.0/3)))/log(pi*x)-y;
//易錯點!!!!!寫成1/3就錯了!!!千萬千萬要注意!
}
int main(){
double i,y;
scanf("%lf",&y);
for(i=0.33;i<=10;i+=1e-6)if(f(i,y)<0)break;
//因為這個函數是單調減的,找到小於零的點就跳出輸出即可
printf("%.5f",i);
return 0;
}
不出所料,這個程序很快地TLE了
然后就開始懷疑人生
什么?我花了好久寫的這么好的程序?就這樣T掉了????
於是,開始想更妙的做法。
重點來了
我們觀察到,這個函數是單調遞減的。也就是說,如果用循環尋找答案的話,是一個非常浪費計算量的事情。我們可以利用二分的思想,每次截掉肯定不合理的左端或右端,寫出以下的程序:
#include<stdio.h>
#include<math.h>
#define pi acos(-1)
double f(double x,double y){
return (sin(sqrt(x))+exp(-pow(x,1.0/3)))/log(pi*x)-y;
}
int main(){
double i,y;
scanf("%lf",&y);
double left=0.33,right=10,mid,eps=1e-10;
while(right-left>eps){//當左右夾的區間太大時
mid=(left+right)/2;
if(f(mid,y)>0)left=mid;//如果大於零,那么刪掉左區間
else right=mid;//否則刪掉右區間
}
printf("%.5f",left);
return 0;
}
這就很快樂了。注意到,這種精度能達到\(1e-10\)的程序只需要\(9ms\),相比之下暴力循環求解精度\(1e-6\)都要\(TLE\)。
其實,可以證明,這種算法的復雜度是\(O(log n)\)的。
回顧一下做這個題的思路。因為函數具有單調性,所以可以通過不斷二分達到不斷縮小答案區間的目的。請仔細理解這句話。
現在再回去看這道題。
從頭開始,倒霉的學弟學妹開始每個人搬一堆書,這道題的意思就是問所有人中,搬書重量最大的那個人搬書重量的最小值。
什么意思呢?
簡單來說,就是把每個人要搬的所有書(假設書的高度與重量成正比)摞起來,再拿一個標尺,問這個標尺至少要多高才能把所有人的書都蓋住。
那么我們現在看的這個標尺,是不是和剛剛的那個函數有點相像啊?
我當然可以通過枚舉標尺的高度得到答案,但是會慢很多。
於是,我們可以二分地尋找這個標尺的高度,這類似尋找剛剛函數的零點。
找到一個答案下限,找到一個答案上限,在這個區間內二分查找合理答案。
請思考:這個答案下限和答案上限分別是多少?
想好了再回來。
不給出答案。直接貼代碼。
#include<stdio.h>
int a[100010];
int main(){
int n,m,i;
while(~scanf("%d%d",&n,&m)){
//~是取反,相當於scanf()!=EOF
//注意這里沒有先初始化a[i]數組,因為沒必要
//但是一定要注意有必要的時候千萬別忘了初始化定義在外面的變量
int left=0,right=0;
for(i=0;i<n;i++){
scanf("%d",&a[i]);
if(a[i]>left)left=a[i];
right+=a[i];
}
while(left<right){
int mid=(left+right)/2;
int count=0,sum=0;
//從這里開始判斷這個標尺(mid)是太高了還是太低了
for(i=0;i<n;i++){
if(sum+a[i]<=mid)sum+=a[i];
else{
sum=a[i];
count++;
}
}
count++;
if(count>m)left=mid+1;//如果標尺太低了,那么就升高
//一定要注意這里的是mid+1而不是mid!!
//請仔細思考這是為什么
else right=mid;//否則降低
}
printf("%d\n",left);
}
return 0;
}
謝謝觀看XD
如果想練習二分的話,可以隨便虐一下下面幾個題(難度大概遞增):
BHOJ T1546 水水的二分查找
BHOJ T529 大家一起來排隊
洛谷 P3382 【模板】三分法
洛谷 P1024 一元三次方程求解
洛谷 P1182 數列分段Section II
洛谷 P1316 丟瓶蓋
BHOJ T121 相位轉移
BHOJ T1564 ModricWang的導彈攔截系統
下周期末考試加油呀!