二分的基础用法是在单调序列或单调函数中进行查找。因此当问题的答案具有单调性时,就可以通过二分把求解转化为判定。其实,写二分的时候并不容易,写check函数的时候,有很多细节之处需要仔细考虑。
- 对于整数域上的二分,需要注意终止边界、左右区分取舍时的开闭请,避免漏掉进入死循环。
- 对于实数域上的二分,需要注意精度问题。
二分的实现模板有很多种,以下给出的模板是yxc的,非常好用。
//模板请记忆
//整数二分
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
//注意,分析题意,画数轴,看mid的右边是答案,还是mid的左边是答案
upd20211115,对于二分模板的认识,我现在认为不应该局限于模板本身,下面的代码更好去理解,也很专业。在初赛真题当中,有很多次二分,具体代码实现的环节,就有两种
while (l < r)
while (l <= r)
这两种在当时判断边界是用填空去写的,比较容易进坑,需要代数模拟。现在这两种写法一起组合,对二分的理解用会深入很多。边界确实是一个头疼的话题,很容错,然后,很难调。
我现在更青睐后者
// 数的范围
// https://www.acwing.com/problem/content/description/791/
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, q, a[N], k;
int solve(int k){
int l = 0, r = n - 1, best = -1;
while (l <= r){
int mid = (l + r) >> 1;
if (a[mid] <= k){
best = mid;
l = mid + 1;
}
else
r = mid - 1;
}
if (a[best] != k) return -1;
return best;
}
int solve2(int k){
int l = 0, r = n - 1, best = -1;
while (l <= r){
int mid = (l + r) >> 1;
if (a[mid] >= k){
best = mid;
r = mid - 1;
}
else
l = mid + 1;
}
if (a[best] != k) return -1;
return best;
}
int main(){
cin >> n >> q;
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
while (q--){
scanf("%d", &k);
int l = solve2(k);
int r = solve(k);
printf("%d %d\n", l, r);
}
return 0;
}
//模板请记忆
//浮点数二分,还有循环50次的写法
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double 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;
}
return l;
}
double bsearch_4(double l, double r)
{
int T = 50;
while (T--)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
例题,和为给定数
//
例题,查找最接近的元素
//