二分的基础用法是在单调序列或单调函数中进行查找。因此当问题的答案具有单调性时,就可以通过二分把求解转化为判定。其实,写二分的时候并不容易,写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;}elser = 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;}elsel = 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;}
例题,和为给定数
//
例题,查找最接近的元素
//
