近几年常见的一类二分套路题,从上古时代 01 规划的基础原理扩展
1. 为什么要二分
假设存在最优解,不妨钦定最优解为 \(X^{*}\) 。这意味着 \(\exists i,j\) 使得
有解。
这意味着 \(\exists i,j\) 使得
有解。
question
这一步怎么来的?
看起来这个式子是不是分母提到右边去?然后再两边减去右值?
肯定不是啊……那么大一个 max() 算子约束在那里……当然,从结果来看确实是这样……所以大家背这类题会这样去记忆公式。
具体原理可以参考证明。
proof
证明:
若存在最优值且为 \(X^{*}\) ,则 \(\forall i,j\) 满足 \(1 \leq i \leq j \leq n, j - i + 1 \geq L\) ,都有
因为所有情况都满足,这里可以直接处理式子
设存在的最优 \(i, j\) 为 \(i^{*}, j^{*}\) ,带入有
则
\(\square\)
若 \(X \neq X^{*}\) ,这意味着 \(\forall i,j\) 使得
设
能分析出 \(G(X)\) 的单调性:\(\forall X_1 < X_2, G(X_1) > G(X_2)\) 。
众所周知单调函数上求解,二分法是很快的做法。
为了得到 \(X^{*}\) 。若 \(G(X) < 0\) ,则让 \(X\) 变大;若 \(G(X) > 0\) ,则让 \(X\) 变小。
2. 怎么去二分
二分得到一个定值 \(X = M\) ,我们要怎么计算 \(G(M)\) ?为了方便计算我们设 \(b_{i} = a_{i} - M\) 。
如果没有 \(j - i + 1 \geq L\) 的限制,就是非常经典的问题
设 \(g(j - 1) = \underset{\substack{1 \leq i \leq j - 1}}{min} \sum_{l = 1}^{i} b_l\) ,即前缀和的最小值函数;
设 \(f(x) = \sum_{i = 1}^{x} b_i\) ,即前缀和函数。于是有 \(g(x) = \underset{\substack{1 \leq i \leq x}}{min} f(i)\) 。
显然有 \(g(x + 1) =\underset{\substack{1 \leq i \leq x + 1}}{min} f(i) = min \left ( \underset{\substack{1 \leq i \leq x}}{min} f(i), f(x + 1) \right )\) 。
这意味着我们如果能 \(O(1)\) 得到 \(f(x + 1)\) ,我们就能 \(O(1)\) 从 \(g(x)\) 得到 \(g(x + 1)\) 。显然 \(f(x)\) 是前缀和函数,众所周知可以 \(O(1)\) 从 \(f(x)\) 得到 \(f(x + 1)\) 。
于是对于 \(F(M)\) (第二个式子)的求解,我们可以 \(O(n)\) 枚举右端点 \(j\) 求出前缀和 \(f(j)\) ,然后顺便递推出 \(g(j - 1)\) 。那么 \(F(M) = \underset{1 \leq j \leq n}{max} \left ( f(j) - g(j - 1) \right )\) 就能线性算出。
实现
代码大概长这样
for (int i = 1; i <= n; i++) {
f[i] = f[i - 1] + a[i];
g[i] = min(g[i - 1], f[i]);
F = max(F, f[i] - g[i - 1]);
}
因为 \(g\) 的前缀是用不到的,我们每次只考虑 \(g(i - 1)\) ,所以也可以写成这样
for (int i = 1; i <= n; i++) {
f[i] = f[i - 1] + a[i];
preMin = min(preMin, f[i - 1]);
F = max(F, f[i] - preMin);
}
有 \(j - i + 1 \geq L\) 的限制也很简单,即是
那么 \(G(M) = \underset{1 \leq j \leq n}{max} \left ( f(j) - g(j - L) \right )\) 。
实现
代码大概长这样
for (int i = 1; i <= n; i++) {
f[i] = f[i - 1] + a[i];
g[i] = min(g[i - 1], f[i]);
if (i >= L) G = max(G, f[i] - g[i - L]);
}
因为 \(g\) 的前缀是用不到的,我们每次只考虑 \(g(i - L)\) ,所以也可以写成这样
for (int i = 1; i <= n; i++) {
f[i] = f[i - 1] + a[i];
if (i >= L) preMin = min(preMin, f[i - L]);
G = max(G, f[i] - preMin);
}
或者说另一个写法
for (int i = 1; i <= n; i++) {
f[i] = f[i - 1] + a[i];
if (i >= L) preMin = min(preMin, f[i - L]);
if (G - preMin >= 0) return true;
}
的意思就是,如果中间存在一对 \(i, j\) 的结果 \(Y_{1} \geq 0\) ,那么最优的 \(i^{*}, j^{*}\) 的结果 \(Y_2\) 会满足 \(Y_{2} \geq Y_{1}\) ,显然已经导致 \(Y_{2} \geq 0\) ,那么我们试图让 \(X\) 再大一点,使得 \(G(X)\) 减小,往 \(0\) 靠近。
这个写法只是常数更好。
浙公网安备 33010602011771号