• 浅谈二分与二分答案


    前言

    二分是个看似很简单的算法 其实它的思想非常的巧妙
    此外 二分中有很多细节也是值得思考的

    下面也讲讲我个人对二分的理解吧

    1.二分的本质

    • 在单调序列或者单调函数中查找

    我们就先拿一个简单的例子好了 寻找单调递增序列(arr)中位置最靠左且(geq x)的数
    在这里我先随便给个序列吧

    1 1 2 3 6 7 9 9
    x = 5
    

    显然答案是(arr_4=6)
    在这里我们可以把原序列转化 我们定义(geq x)为一个条件 将序列中所有数分为满足条件和不满足条件
    我们定义一个函数(f(x)) 这个函数的返回值只有可能是0或1
    如果(arr_igeq x)(arr_i)满足条件 那么(f(i)=1)
    否则(f(i)=0)

    arr  1 1 2 3 6 7 9 9
    f(x) 0 0 0 0 1 1 1 1
    

    我们要寻找下标最小的满足条件的数
    问题就被转化成了寻找最左边的1
    其实原序列即使是个不单调的也无所谓 只要我们最后转化成的01序列是单调的 就可以进行二分

    如果序列是单调递减的 要找最右边的满足(geq x)的数:

    arr  9 9 7 6 3 2 1 1
    f(x) 1 1 1 1 0 0 0 0
    

    我们要找到最右边的1的位置
    我们可以把它看作最左边的0的位置减1 显然这是对的
    注意全是0的情况

    当然我们有时候也会去找最大/最小的不符合条件的数 本质都比较相似
    这个条件是我们根据题目自己定义的 只要满足这个条件下(f(x))具有单调性 这个条件怎么选都是可以的

    所有的二分问题 就可以被转化成这样的形式

    eg:查找一个单调序列中 (x) 出现了多少次
    两次二分 条件分别是(geq x)(>x)

    arr  1 1 2 4 5 5 5 6 7 7 8
    f(x) 0 0 0 0 1 1 1 1 1 1 1
                i^
    g(x) 0 0 0 0 0 0 0 1 1 1 1
                      j^
    

    两个都找最左边的1 (j-i)就是答案

    2.二分答案思想

    这题举例子
    把条件设为"每段和的最大值(leq x)"
    能满足则(f(x)=1) 不能则(f(x)=0)
    对于样例得到这样一个序列

    x    1 2 3 4 5 6 7 8 9
    f(x) 0 0 0 0 0 1 1 1 1
    

    f(x)的计算方式很简单...这里就略过了
    找到最左边的1 它就是答案

    二分答案通过这种方式把一个最优化问题转化成判断可行性问题
    单调性是前提

    3.二分边界问题

    很关键
    我的二分一般都是这么写的
    假设在这样一个序列上做二分

    x    1 2 3 4 5 6 7 8 ... n
    f(x) 0 0 0 0 1 1 1 1 ... 1
    

    代码:

    while(l <= r){
    	mid = (l + r) >> 1; // (l+r)/2
        if(f(mid) == 1) r = mid - 1;
        else l = mid + 1;
    }
    // l是答案
    

    如果mid=1 那么([mid,n])一定都是1
    如果mid=0 那么([1,mid])一定都是0
    这样的话一定要做到(l>r)
    因为在这样的情况下

    0 0 0 ? 1 1 1 1
         l^r
    

    由于l = mid + 1r = mid - 1 l和r所在位置并没有被判断过是0或者是1 这里也要判断

    有时候我们要找最右边的0 直接把l-1就可以了

    如果序列是这样的

    x    1 2 3 4 5 6 7 8 ... n
    f(x) 1 1 1 1 0 0 0 0 ... 0
    

    就照常写

    while(l <= r){
    	mid = (l + r) >> 1; // (l+r)/2
        if(f(mid) == 0) r = mid - 1;
        else l = mid + 1;
    }
    // l是答案
    

    找到最左边的0
    找最右边的1就把l-1

    还有一种写法 比较保险一点 就是在每次查找到(f(x))的值符合你想要的值的时候 记录ans=mid 最后ans是答案

    while(l <= r){
    	mid = (l + r) >> 1; // (l+r)/2
        if(f(mid) == 1){
        	ans = mid;
            r = mid - 1;
        }
        else l = mid + 1;
    }
    // ans是答案
    
  • 相关阅读:
    使用SpringAop 验证方法参数是否合法
    log4jdbc-remix安装配置
    mybatis和spring3.1整合
    MyBatis-Spring 执行SQL语句的流程
    SSH配置log4j的方法
    Drupal 判断匿名用户必须先登录的解决方法
    Drupal 出错的解决办法
    crontab执行PHP
    本地生成Rails API文档
    一个根据身份证号获取的程序
  • 原文地址:https://www.cnblogs.com/IltzInstallBI/p/12744056.html
Copyright © 2020-2023  润新知