• 基础算法·二分答案


    题目链接

    摸鱼助教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++!

  • 相关阅读:
    计算欧拉函数值
    矩阵快速幂
    约瑟夫环数学公式
    整型输出输入优化
    计算机设计第三章
    计算机设计第二章
    计算机设计
    阿里巴巴秋招2017客户端附加题
    程序设计基本概念
    c++面试题
  • 原文地址:https://www.cnblogs.com/Potassium/p/10125386.html
Copyright © 2020-2023  润新知