基础算法·二分答案

题目链接

摸鱼助教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
",left);
	}
	return 0;
}

谢谢观看XD

如果想练习二分的话,可以随便虐一下下面几个题(难度大概递增):

BHOJ T1546 水水的二分查找

BHOJ T529 大家一起来排队

洛谷 P3382 【模板】三分法

洛谷 P1024 一元三次方程求解

洛谷 P1182 数列分段Section II

洛谷 P1316 丢瓶盖

BHOJ T121 相位转移

BHOJ T1564 ModricWang的导弹拦截系统

下周期末考试加油呀!

祝大家期末考试rp++!

原文地址:https://www.cnblogs.com/Potassium/p/10125386.html