Adva::0x04
作者:互联网
Adva::0x04 二分
Part 1. 引言
二分法是一种随处可见却非常精妙的算法,基础用法是在单调序列 / 函数中进行查找。当问题的答案具有单调性时,我们可以通过二分将求解转化为判定。
据说,只有 \(10\%\) 的程序员能写对二分(?)。二分的方法多种多样,需要注意边界,取舍,精度等。
Part 2. 二分基本模板
- 最大值最小化
while (l < r) {
int mid = (l + r) >> 1;
if (check(mid)) {
r = mid;
} else {
l = mid + 1;
}
}
- 最小值最大化
while (l < r) {
int mid = (l + r + 1) >> 1;
if (check(mid)) {
l = mid;
} else {
r = mid - 1;
}
}
我们考虑第一种方法无解当 \(mid = r\);第二种方法无解当 \(mid = l\)。我们可以利用这一性质处理无解的情况。
- 实数域二分
while (l + eps < r) {
double mid = (l + r) / 2;
if (check(mid)) {
r = mid;
} else {
l = mid;
}
}
另外,这种方法有时使用下面的写法精度会更高:
for (int i = 0; i < 100; ++i) {
double mid = (l + r) / 2;
if (check(mid)) {
r = mid;
} else {
l = mid;
}
}
Part 3. 二分答案转化为判定
我们在处理算法问题时,常遇到这种问题:对于最优解 \(S\),在 \(S\) 的一侧均不合法,而在另一侧均合法。形式化地说:若评分越高越优,则 \(\forall x > S\),都不合法,而 \(\forall x \leq S\),都有方案使得存在合法方案 \(x_0 \geq x\)。
例题
有 \(n\) 本书排成一行,厚度为 \(a_1 \sim a_n\)。
把它们分成连续 \(m\) 组,使最大组厚度之和最小。
我们二分最大厚度之和,每次 check
判断能否被分为最多 \(m\) 组。
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <stack>
#include <cstring>
#include <climits>
using namespace std;
const int kMaxN = 1e5 + 5;
int n, m, a[kMaxN], r;
bool check(int t) {
int groups = 0, sum = 0;
for (int i = 1; i <= n; ++i) {
if (sum + a[i] <= t) sum += a[i];
else ++groups, sum = a[i];
}
return groups < m;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
r += a[i];
}
int l = 0;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << l << endl;
return 0;
}
Best Cow Fences
给定正整数数列 \(A\),求一个平均数最大的,长度不小于 \(L\) 的子段。
二分答案。判定:是否存在一个长度不小于 \(L\) 的子段,平均数不小于二分的值。
一个小 trick:每个数减去二分的值,然后判定有无非负子段和。
我们考虑问题“求一个长度不小于 \(L\) 的子段最大和”。
\[\max_{i - j \geq L}\left\{\sum{a_k}\right\} = \max_{L \leq i \leq n}\left\{sum_i - \min_{0 \leq j \leq i - L}\left\{sum_j\right\}\right\} \]故我们维护前缀和 \(sum\) 的前缀最小值即可。
#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
#include <stack>
#include <cstring>
#include <climits>
using namespace std;
const int kMaxN = 1e5 + 5;
int n, f, a[kMaxN];
double b[kMaxN], sum[kMaxN];
bool check(double x) {
for (int i = 1; i <= n; ++i) {
b[i] = a[i] - x;
}
for (int i = 1; i <= n; ++i) {
sum[i] = sum[i - 1] + b[i];
}
double ans = -1e10, minval = 1e10;
for (int i = f; i <= n; ++i) {
minval = min(minval, sum[i - f]);
ans = max(ans, sum[i] - minval);
}
return ans >= 0;
}
int main() {
cin >> n >> f;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
double eps = 1e-6, l = -1e6, r = 1e6;
while (l + eps < r) {
double mid = (l + r) / 2;
if (check(mid)) {
l = mid;
} else {
r = mid;
}
}
cout << int(1000 * r) << endl;
return 0;
}
Innovative Business
有 \(n\) 个元素,每一对元素之间的大小关系是确定的。你必须通过不超过 \(10000\) 次询问,每次询问得知两个数之间的大小关系,来把这 \(n\) 个数排好序。
我们考虑已经将 \(k - 1\) 个元素排好序,插入第 \(k\) 个元素。
于是我们二分 \(a_k\) 的位置,所有共 \(n \times \log_2n\) 次询问。
// Forward declaration of compare API.
// bool compare(int a, int b);
// return bool means whether a is less than b.
class Solution {
public:
vector<int> specialSort(int N) {
vector<int> v; v.push_back(1);
for (int i = 2; i <= N; ++i) {
int l = 0, r = v.size() - 1;
while (l <= r) {
int mid = l + r >> 1;
if (compare(v[mid], i)) {
l = mid + 1;
} else {
r = mid - 1;
}
}
v.push_back(i);
for (int j = v.size() - 2; j > r; --j) swap(v[j], v[j + 1]);
}
return v;
}
};
标签:二分,0x04,Adva,mid,else,int,include,check 来源: https://www.cnblogs.com/reliauk/p/0x04.html