基础算法·二分答案
题目链接
洛谷原题(除了多组数据都是相同的)链接:
解题思路
二分答案。
什么?什么是二分答案?我没听过
不要紧,希望这篇文章能帮助不会二分答案的你更好地理解二分的思想。
(神犇求放过)
不扯了,谈正题。
大家都做过一个单调递增函数找零点的题吧,比如这个
还记得怎么做么?什么,没记得做过这个题?那请您先去做一遍。
先回到最初的起点。
在我们初入程设的时候,循环语句就成为陪伴我们的梦魇朋友。就拿这个题举例子吧,我可以写一个这样的程序来做这道题:
#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的导弹拦截系统
下周期末考试加油呀!