浅谈二分与二分答案

前言

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

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

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是答案
原文地址:https://www.cnblogs.com/IltzInstallBI/p/12744056.html