近几年常见的一类二分套路题,从上古时代 01 规划的基础原理扩展

1. 为什么要二分

假设存在最优解,不妨钦定最优解为 \(X^{*}\) 。这意味着 \(\exists i,j\) 使得

\[\underset{\substack{1 \leq i \leq j \leq n, \\ i - j + 1 \geq L}}{max} \left ( \frac{\sum_{k = i}^{j} a_k}{j - i + 1} \right ) = X^{*} \]

有解。

这意味着 \(\exists i,j\) 使得

\[\underset{\substack{1 \leq i \leq j \leq n, \\ i - j + 1 \geq L}}{max} \left ( \sum_{k = i}^{j} a_k - X^{*} \right ) = 0 \]

有解。


question

这一步怎么来的?

看起来这个式子是不是分母提到右边去?然后再两边减去右值?

肯定不是啊……那么大一个 max() 算子约束在那里……当然,从结果来看确实是这样……所以大家背这类题会这样去记忆公式。

具体原理可以参考证明。


proof

证明:
若存在最优值且为 \(X^{*}\) ,则 \(\forall i,j\) 满足 \(1 \leq i \leq j \leq n, j - i + 1 \geq L\) ,都有

\[\frac{\sum_{k = i}^{j} a_k}{j - i + 1} \leq X^{*} \]

因为所有情况都满足,这里可以直接处理式子

\[\frac{\sum_{k = i}^{j} a_k}{j - i + 1} \leq X^{*} \overset{\substack{显然不 \\ 会变号}}{\Longrightarrow} \sum_{k = i}^{j} a_k \leq X^{*} (j - i + 1) \Longrightarrow \sum_{k = i}^{j} (a_k - X^{*}) \leq 0 \]

设存在的最优 \(i, j\)\(i^{*}, j^{*}\) ,带入有

\[\sum_{k = i^{*}}^{j^{*}} (a_k - X^{*}) = 0 \]

\[\underset{\substack{1 \leq i \leq j \leq n, \\ i - j + 1 \geq L}}{max} \left ( \sum_{k = i}^{j} a_k - X^{*} \right ) = 0 \]

\(\square\)


\(X \neq X^{*}\) ,这意味着 \(\forall i,j\) 使得

\[\underset{\substack{1 \leq i \leq j \leq n, \\ i - j + 1 \geq L}}{max} \left ( \sum_{k = i}^{j} a_k - X^{'} \right ) \neq 0 \]

\[G(X) = \underset{\substack{1 \leq i \leq j \leq n, \\ i - j + 1 \geq L}}{max} \left ( \sum_{k = i}^{j} a_k - X \right ) \]

能分析出 \(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\) 的限制,就是非常经典的问题

\[F(M) = \underset{\substack{1 \leq i \leq j \leq n}}{max} \left ( \sum_{k = i}^{j} b_k \right ) = \underset{\substack{1 \leq j \leq n}}{max} \left ( \sum_{k = 1}^{j} b_k - \underset{\substack{1 \leq i < j}}{min} \sum_{l = 1}^{i} b_l \right ) \]

\(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{\substack{1 \leq i \leq j \leq n, \\ j - i + 1 \geq L}}{max} \left ( \sum_{k = i}^{j} b_k \right ) = \underset{\substack{1 \leq j \leq n}}{max} \left ( \sum_{k = 1}^{j} b_k - \underset{\substack{1 \leq i < j - L + 1}}{min} \sum_{l = 1}^{i} b_l \right ) \]

那么 \(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\) 靠近。

这个写法只是常数更好。

posted @ 2025-08-01 06:11  03Goose  阅读(19)  评论(0)    收藏  举报