• 二分搜索与二分答案


    二分

    •主要用于在一个单调的函数中查询某值

     连续函数的情况:

    • 若当前查找的区间是 [l, r] ,查询的值是 y ,函数单增

    • 设 mid = (l + r) / 2 若 f(mid) < y 则 l = mid, 否则 r = mid

    • 直至 r - l < eps

    离散函数的情况:

    • 当前查找的区间是 [l, r] ,查询的值是 y ,函数单增

    • 设 mid = (l + r) / 2 若 f(mid) = y 则 return mid

    • 若 f(mid) < y 则 l = mid + 1, 否则 r = mid - 1

    二分法

    经常有这样的问题,求xxx最小值的最大值,即求符合条件的值里的最大值,这种问题有个解法叫二分答案法。一听,什么,不知道的答案也能二分?嗯没错,关键在于这个答案是可以判断是不是符合条件的。

    算法思想

    以求最小值的最大值(最小值最大化)为例,尝试一个可能的答案,如果这个答案符合题目条件,那么它肯定是“最小”(可行解),但不一定是“最大”(最优解),然后我们换个更大的可能答案,如果也符合条件,那这个新可行解就更优,不断重复即可。怎么找呢?这时就该二分上场了。

    二分答案的主要思想

    • 就是在答案的可能范围(区间)内二分枚举

    • 并检查所穷举的答案是否符合题意。

    • 可以将最优性问题(直接求解相对较难)

    • 转化为可行性问题(答案是否符合题意相对容易)

    二分答案的适用范围(条件)

    • 二分答案当且仅当答案的范围已知且具有单调性的时候才可以使用。

    • 一般也是求最优值问题• 更多、更明显的标志是:

    • “最大值最小化” 或者 “最小值最大化”

    二分答案的框架• 假设答案是单调递增的,要求的是“最小值”

    • l = 答案下限,r = 答案上限

    • while (l <= r)

    • {

            • mid = (l + r) >> 1;

            • if check(mid) ans = mid, r = mid - 1; else l = mid + 1;

    • }

    • return ans;

    二分答案的难点(关键)

    • 如何检验当前的答案是否符合题目的要求(限制条件)?

    • 常见的方法:穷举、贪心、搜索、动态规划、图论、数据结构等

    • 可以看到,二分答案问题很好地结合了其他算法知识,非常受命题者欢迎

    • NOIP 2010 以来经常出现,例如 NOIP 2015 D1T2 跳⽯头

    二分前提

    1.答案区间上下限确定,即最终答案在哪个范围是容易知道的。

    2.检验某值是否可行是个简单活,即给你个值,你能很容易的判断是不是符合题目要求。

    3.可行解满足区间单调性,即若x是可行解,则在答案区间内x+1(也可能是x-1)也可行。

    两种情况

    下图中L,R为当前答案区间,M为中心点,根据二分思想判断M是否符合条件,再移动L或R,变成L',R',图中的T和F表示是否符合条件。

    1.最小值最大化

    int l = min_ans, r = max_ans;
    while (l < r) {
    	int mid = (l + r + 1) / 2;   //+1避免 r == l + 1 时mid一直等于l,从而死循环
    	if (ok(mid))	//符合条件返回True
    		l = mid;
    	else
    		r = mid - 1;
    }
    

      

    希望答案尽可能大,所以我们需要确保左区间L点符合题目条件(最小),至于R是否符合条件是不确定的,首先判断M点符合与否,符合则将L移到M点,维持了L的True属性,也增大了所要的最小值所在区间,如果不符合,没办法在保持L的True属性情况下移动L,那就移动R。

    2.最大值最小化

    int l = min_ans, r = max_ans;
    while (l < r) {
    	int mid = (l + r) / 2;
    	if (ok(mid))	//符合条件返回True
    		r = mid;
    	else
    		l = mid + 1;
    }
    

      

    按同样道理分析,维持R的True属性即可。这里的mid就不需要加1了,因为 mid 跟 l 重合时,l = mid + 1;会自增,而当 mid 和 r 重合时 l 也跟 r 重合,结束循环了。

    注意点

    1. 每次循环都要确保L和R有一个被更新,否则死循环就呵呵了。
    2. 答案是浮点数的情况:区间更新不能加1,这样变动太大,直接
    l = mid;  
    r = mid;  
    

      

    三分法

    当二分的函数值不是递增/减,而是先增后减或者先减后增时二分就挂了。此时需要三分法,这里直接盗用hihocoder Problem 1142的图(hihocoder需要注册登陆,没登陆进不去)

     

    如图这种情况先减后增有极小,若lm比rm低(即lm对应的函数值 < rm函数值)则极小点(图中最低点)肯定在[ left, rm ] ,反之在[ lm, right ],剩下就跟二分一样根据大小关系调整区间就行了。那lm和rm取值多少?一个不错的取值是lm为整个区间的1/3点,rm为2/3点,即

    lmid = l + (r - l)/3;
    rmid = r - (r - l)/3;
    

      

    嗯三分就这样完了。

     

    然后另外一种情况,先增后减有极大:

    如图lm低于rm,则极大在[ lm,right ](为啥不是[ left, rm ]?你试试把rm放在lm右边,极大值左边看看?),否则极大在 [ left, rm ]。写代码上就是极小的处理语句反过来就行了。
     

    HDU 2899 Strange fuction

    给一函数,该函数在任意Y>0的情况下x在[0,100]内有极小值,求之。
     
    按思路套上代码即可AC
    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    double y;
    double val(double x){
        return 6*x*x*x*x*x*x*x+8*x*x*x*x*x*x+7*x*x*x+5*x*x-y*x;
    }
    
    double solve(double l,double r){
        double eps = 1e-7;
        while(l + eps < r){
            double lmid = l + (r-l)/3,rmid = r - (r-l)/3;
            if(val(lmid) < val(rmid)){
                r = rmid;
            }else{
                l = lmid;
            }
        }
        return val(l);
    }
    int main(){
        int t;
        cin>>t;
        while(t--){
            cin>>y;
            printf("%.4f
    ", solve(0,100.0));
        }
        return 0;
    }
    

      

    有一条抛物线y=ax^2+bx+c和一个点P(x,y),求点P到抛物线的最短距离d。  
    输入  
      
    第1行:5个整数a,b,c,x,y。前三个数构成抛物线的参数,后两个数x,y表示P点坐标。-200≤a,b,c,x,y≤200  
      
    输出  
      
    第1行:1个实数d,保留3位小数(四舍五入)  
      
    样例输入  
    2 8 2 -2 6  
    样例输出  
    2.437 
    #include <cstdio>
    #include <cmath>
    #include <cstring>
    #include <string>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define ll long long
    #define clr( a , x ) memset ( a , x , sizeof (a) );
    #define RE freopen("1.in","r",stdin);
    #define WE freopen("1.out","w",stdout);
    #define SpeedUp std::cout.sync_with_stdio(false);
    const int maxn = 1e5+5;
    const int inf = 0x3f3f3f3f;
    double a,b,c,x,y;
    
    double val(double X){
        return sqrt((X-x)*(X-x)+(a*X*X+b*X+c-y)*(a*X*X+b*X+c-y));
    }
    
    double solve(double l,double r){
        double eps = 1e-5;
        while(l+eps<r){
            double lmid = l + (r-l)/3,rmid = r - (r-l)/3;
            if(val(lmid) < val(rmid)){
                r = rmid;
            }else{
                l = lmid;
            }
        }
        return val(l);
    }
    int main(){
        // RE
        while(cin>>a>>b>>c>>x>>y){
            printf("%.3f
    ", solve(-200.0,200.0));
        }
        return 0;
    }
    

      

     
  • 相关阅读:
    Oracle常用命令大全(很有用,做笔记)
    表格驱动编程在代码中的应用
    mac 利用svn下载远程代码出现Agreeing to the Xcode/iOS license requires admin privileges, please re-run as root via sudo.
    FAILURE: Build failed with an exception.
    There is an internal error in the React performance measurement code.Did not expect componentDidMount timer to start while render timer is still in progress for another instance
    react native TypeError network request failed
    Android向系统相册中插入图片,相册中会出现两张 一样的图片(只是图片大小不一致)
    react-native Unrecognized font family ‘Lonicons’;
    react-native SyntaxError xxxxx/xx.js:Unexpected token (23:24)
    Application MyTest has not been registered. This is either due to a require() error during initialization or failure to call AppRegistry.registerComponent.
  • 原文地址:https://www.cnblogs.com/Roni-i/p/8672307.html
Copyright © 2020-2023  润新知