基于 2D 解析几何,将 DP 状态方程映射为直线截距式,通过单调队列维护凸包切线,降维复杂度至 $O(N)$
对于形式如 $dp[i] = \min \{ dp[j] + (s[i] - s[j])^2 \}$ 的 DP 方程,朴素转移为 $O(N^2)$。当决策点含有 $i$ 与 $j$ 的乘积项时,可将其化归为 解析几何直线斜截式。
我们将只含 $j$ 的项设为 $y$ 和 $x$,含 $i$ 且与 $j$ 相乘的项设为斜率 $k$。方程化归为:
其中,$b$ 包含待求状态 $dp[i]$(例如 $b = dp[i] - s[i]^2$)。要使得 $dp[i]$ 最小,等价于寻找一个已知斜率 $k$ 的直线,在穿过决策点集中的某个点 $(x_j, y_j)$ 时,在 $Y$ 轴上的截距 $b$ 达到最小。
double getSlope(int i, int j) {
return (double)(y(j) - y(i)) / (x(j) - x(i));
}
void solve() {
int head = 1, tail = 1;
q[1] = 0; // 初始化单调队列
for (int i = 1; i <= n; i++) {
// 1. 头部出队:斜率单调递增,剔除切点左侧的不优元素
while (head < tail && getSlope(q[head], q[head+1]) < 2 * s[i]) {
head++;
}
int best_j = q[head]; // 2. 队首即为当前斜率下的最优决策点!
dp[i] = dp[best_j] + (s[i] - s[best_j])^2;
// 3. 尾部出队:新决策点入队,剔除违反下凸壳性质的凹陷元素
while (head < tail && getSlope(q[tail-1], q[tail]) > getSlope(q[tail], i)) {
tail--;
}
q[++tail] = i; // 4. 新决策点入队
}
}