基礎算法·二分答案


題目鏈接

摸魚助教Mogg Ⅱ

洛谷原題(除了多組數據都是相同的)鏈接:

P1182 數列分段Section II

解題思路

二分答案。

什么?什么是二分答案?我沒聽過

不要緊,希望這篇文章能幫助不會二分答案的你更好地理解二分的思想。

(神犇求放過)

不扯了,談正題。

大家都做過一個單調遞增函數找零點的題吧,比如這個

還記得怎么做么?什么,沒記得做過這個題?那請您先去做一遍。

先回到最初的起點。

在我們初入程設的時候,循環語句就成為陪伴我們的夢魘朋友。就拿這個題舉例子吧,我可以寫一個這樣的程序來做這道題:

#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了
顯示不出來的話換谷歌或者360瀏覽器吧

然后就開始懷疑人生

什么?我花了好久寫的這么好的程序?就這樣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;
}

顯示不出來的話換谷歌或者360瀏覽器吧

這就很快樂了。注意到,這種精度能達到\(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的導彈攔截系統

下周期末考試加油呀!

祝大家期末考試rp++!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM