第一次了解二分是在高中的数学课,当时没觉得多难,毕竟思想是很简单的。但在代码实现上却会遇到很多困难,最常见的也是最难的就是整数二分的边界问题。
整数二分
主要思路
整数二分的思路是这样的:将整数序列分为两端,分界满足的条件,将区间化为左右两个部分,左边满足这个条件,右边不能满足这个条件(或者相反)。此时就用二分来来查找左右两部分的边界。所以所谓二分算法,就是我们知道当前的候选区间中,一定存在我们要找到的答案,而且我们发现这个区间拥有单调性质此类的性质,那么我们可以不停地缩减候选区间的范围,达到排除无用答案的效果,也就是找到正确答案(边界)。
整数二分是有两个边界的,分别是左边的边界与右边的边界,即左右两半部分的边界不是同一个点,而是相邻的2个点,就像下面这个草图:
这就让我们的二分有两个模板,一个是找左边红色的边界,一个是找右边绿色的边界。实际运用时,先不考虑用哪个模板,而是先写check()函数,然后写模板(不考虑mid是否+1),写到if(check(mid))时,再考虑满足check(mid)条件的段是在哪一边:如果在左边红色,则填l = mid;如果在右边,则填r = mid。然后填出else的部分,最后看else后如果填写的是-,则要在mid声明处+1;反之不补。(简单的记忆就是,仅当采用l = mid这种更新方式时,计算mid时需要加1。)
模板
bool check(int x){/*检查x是否满足某个条件*/};
//二分
void bsearch_one(int l,int r){
while(l < r){
int mid = (l + r) >> 1;
if(check(mid)) r = mid;//检查
else l = mid + 1;
}
}
void bsearch_two(int l,int r){
while(l < r){
int mid = (l + r + 1) >> 1;
if(check(mid)) l = mid;//检查
else r = mid - 1;
}
}
例子
给定一个按照升序排列的长度为 nn 的整数数组,以及 qq 个查询。
对于每个查询,返回一个元素 kk 的起始位置和终止位置(位置从 00 开始计数)。
如果数组中不存在该元素,则返回 -1 -1。
输入格式
第一行包含整数 nn 和 qq,表示数组长度和询问个数。
第二行包含 nn 个整数(均在 1∼100001∼10000 范围内),表示完整数组。
接下来 qq 行,每行包含一个整数 kk,表示一个询问元素。
输出格式
共 qq 行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回 -1 -1。
数据范围
1≤n≤1000001≤n≤100000
1≤q≤100001≤q≤10000
1≤k≤100001≤k≤10000
输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
//foreverking
#include <vector>
#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
const int N = 100001;
int n,q,x;
int a[N];
int main(){
cin >> n >> q;
for(int i = 0; i < n; i++) cin >> a[i];
while(q--){
cin >> x;
int l = 0,r = n - 1;
while(l < r){
int mid = (l + r) >> 1;
if(a[mid] >= x) r = mid;
else l = mid + 1;
}
if(a[l] != x) cout << "-1 -1" << endl;
else{
cout << l << ' ';
//找左边界,第二个模板
int l = 0,r = n - 1;
while(l < r){
int mid = (l + r + 1) >> 1;
if(a[mid] <= x) l = mid;
else r = mid - 1;
}
cout << l << endl;
}
}
return 0;
}
分别找出左边边界和右边边界就是答案,找不到就输出-1。
浮点二分
浮点二分就简单许多,因为没有边界问题。
模板
//浮点二分
bool check(double x) {/* ... */} // 检查x是否满足某种性质
void bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
}