今天记录二分知识点。
二分是一个简单清晰,实用性强的算法。
也是本人最喜欢的算法之一。
先给出二分模板吧!
int l = 1, r = n;//初始值,根据情况而定while (l + 1 < r) {int mid = (l + r) >> 1;if (check(mid)) l = mid;// check函数判断左半部分是否不符合,更新 lelse r = mid;// 否则更改 r}
Part one: 二分搜索
二分是一种高效的查找方法,它会将搜索范围一分为二,减小搜索范围,直到找到目标或确定目标不存在。一般可以将时间复杂度从 \(O(n)\) 优化到 \(O(\log_2 n)\)。
但是,二分有个前提要求,就是应具有单调性,比如单调不递减(升序)序列。
\(edg.\) 在一个升序序列 \(s\) 中找到一个指定值 \(x\)。令 $n = |s| $, \(n \leq 10^6\)。
大家想到的第一种方法应该是遍历整个序列找 \(x\) 吧。这种方法的时间复杂度为 \(O(n)\),在某些时候并不足够优秀,可能导致 \(TLE\)。
这时,大名鼎鼎的二分就能发挥其作用了。
先看一段代码:
scanf("%d", &x); // 查找的数字 stable_sort(a + 1, a + n + 1);// 排序,使序列具有单调性,才能二分 int l = 1, r = n;while (l + 1 < r) {int mid = (l + r) >> 1;if (a[mid] <= x) l = mid;else r = mid;}pos = l;printf("%d", pos);
二分的第一步就是确定查找的范围,即 \(l\) 和 \(r\) 的初始值。
在序列中二分时,\(l\) 一般是 \(1\),\(r\) 一般是序列最后一位的下标。
二分时,首先找出中间值,即 mid = (l + r) >> 1
,等价于 mid = (l + r) / 2
。
然后,我们需要更新左右端点的值。
若 \(mid\) 左边的数已经不满足条件,就可以把 \(mid\) 左边舍弃,将左端点( \(l\) )更新为 \(mid\)。
否则,缩小范围,既然左边满足条件,我们只需关注左边的部分,将右端点( \(r\) )更新为 \(mid\)。
此时,区间就缩小到 \((l, mid)\) 或 \((mid, r)\)了。
之后不断如此操作,就能将时间复杂度大大减少。
看模板,此时的 \(check(mid)\) 函数为:a[mid] <= x
。
Part two:二分答案
什么是二分答案呢?顾名思义,我们二分的不再是坐标,而是此题的答案。
同样重要的是单调性!答案具有单调性,才能使用二分大法。
再次看到二分模板。
int l = 1, r = n;
while (l + 1 < r) {int mid = (l + r) >> 1;if (check(mid)) l = mid;else r = mid;
}
此时,二分中的 \(check\) 函数便是判断 \(mid\) 是否满足要求,然后与二分查找一样,减短 \(l\),\(r\) 的范围。
是不是很 easy 呢?光说不练假本事,上道例题。
luogu P2440
题目大意人话:
使 \(\sum_{i = 1} ^ {n}\lfloor \frac{a_i}{l} \rfloor = k\) 的 \(l\) 的最大值,即 \(l_\max\)。
不难发现,随着 \(l\) 的不断增大,总和在不断减少。这就是我们梦寐以求的单调性!接下来,我们就可以快乐地写二分了。
想想 check
函数怎么写。
没错,记录当 \(l = x\) 时能砍下的总段数 \(sum\),判断是否 \(sum \geq k\) 即可。
如此代码:
inline bool check(int x) {int ret = 0;for (int i = 1; i <= n; ++i)ret += a[i] / x;return ret >= k;
}
如果满足,由于求满足条件的最大值,\(mid\) 左边就没有价值去搜索了,将目光转向右边,即将左端点更新为 \(mid\)。
否则,同理,将右端点更新为 \(mid\)。
最后答案便储存在 \(l\) 里了。
你的第一道二分题就 \(Accepted\) 啦。
#include <bits/stdc++.h>
#define ll long long
#define pii pair <int, int>
using namespace std;
const int N = 1e5 + 10;
int a[N], n, k;
inline bool check(int x) {int ret = 0;for (int i = 1; i <= n; ++i)ret += a[i] / x;return ret >= k;
}
int main() {scanf("%d%d", &n, &k);int l = 0, r = 1;//注意有无解情况,l的初始值为0。for (int i = 1; i <= n; ++i) {scanf("%d", &a[i]);r = max(r, a[i]);}while (l + 1 < r) {int mid = (l + r) >> 1;if (check(mid)) l = mid;else r = mid;}printf("%d", l);return 0;
}
这里有一个题单,同学们一起进步吧~