• 基础算法·二分答案


    题目链接

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

  • 相关阅读:
    poj 3528 (三维几何求凸包+凸包表面积)
    dijkstra模板(好像是斐波那契额堆优化,但我为什么看起来像优先队列优化,和spfa一样)
    最大空凸包模板
    ICPC 2017–2018, NEERC, Northern Subregional Contest St Petersburg, November 4, 2017 I题
    hdu 5248 序列变换
    hdu 2063(二分图模板测试)
    组合数
    85. Maximal Rectangle 由1拼出的最大矩形
    750. Number Of Corner Rectangles四周是点的矩形个数
    801. Minimum Swaps To Make Sequences Increasing 为使两个数组严格递增,所需要的最小交换次数
  • 原文地址:https://www.cnblogs.com/Potassium/p/10125386.html
Copyright © 2020-2023  润新知