二分用法总结(一)
二分是一个加速搜索的工具,对于有序的内容,可以通过二分来改善遍历的低效率,而有序的内容不限定是数字,可以是任何有序的.
二分最常见的解法思路步骤是
- 转化题意
- 发掘其中有序的部分
- 建立顺序标识运算函数
f
- 二分查找有序部分
- 输出结果
在更复杂的竞赛中,二分只会是整个题目的一小部分,常见的工具有lower_bound/upper_bound
,常见的复杂题目的配合算法和解题手段有预处理,二分答案
另外注意到数值类的二分,注意溢出,对于可能溢出的解决方案
一个是使用更大的数据类型如long long
另一个是判断符号,需要的时候使用l+(l-r)/2
来代替(l+r)/2
技巧
- 数值类,字符类,
pair<>
类可比较用lower_bound
直接查找,获取查找结果的下标
lower_bound(arr.begin(),arr.end(),val) - arr.begin();
- 每个状态有自己的函数,且上下界一个满足一个不满足, 在二分过程中,保持这两个界所对应的满足关系
while(l+1 < r){ // 始终不会相等
auto m = (l+r)/2;
if(f(m,target)){ // 比较函数 满足
r = m;
}else{ // 不满足
l = m;
}
}
return l;
- 每个状态有自己的函数,且上界满足,下届未知,对于未知的,最终要让它变为满足
while(l < r){ // 会相等
auto m = (l+r)/2;
if(f(m,target)){ // 比较函数 满足
r = m;
}else{ // 不满足
l = m + 1;
}
}
return l;
练手题目
题目1
这道题可以用技巧2, 明确能找到满足和不满足的上下界二分, 运算比较函数如下
bool f(int m,int x) {
return m*m > x;
}
题目2
这道题可以用技巧1, 或技巧3, 直接用lower_bound
或者手写上下界查找, 运算比较函数如下
bool f(int m,int v){
return a[m] < v;
}
题目3
这道题可以用技巧1, 通过分治得到的局部的子数组,再对子数组直接求下标, 对于有偏移量的查询, 用偏移量的指针替代.begin()
,.end()
即可
int idx = lower_bound(A+l,A+r+1,target) - A;
题目4
这道题可以使用技巧1, 先通过分治把二维数组降低到一维, 再用二分找下标
int idx = lower_bound(A.begin(),A.end(),target) - A;
题目5
这道题可以使用技巧1, 和上面题目2是同样的,唯一区别是这个要找恰好相当,而上面找大于等于,只是输出时不同,查找是相同的,运算比较函数也是
bool f(int m,int v){
return a[m] < v;
}