HDU3507.Print Article 题解 斜率优化DP
题目链接:https://acm.hdu.edu.cn/showproblem.php?pid=3507
题目大意:
将数列分成若干段,每一段的代价为这一段所有元素和的平方加上 \(m\)。求:最小总代价。
解题思路:
设 sum[i] 为前缀和,dp[i] 表示前 i 个数分段的最小总代价。枚举最后一段的起点,有转移:
\[dp[i] = \min_{0 \le j < i} \left\{ dp[j] + (sum[i] - sum[j])^2 + m \right\}
\]
展开后:
\[dp[i] = sum[i]^2 + m + \min_{j < i} \left\{ dp[j] + sum[j]^2 - 2sum[i] \cdot sum[j] \right\}
\]
令 \(X(j) = 2sum[j]\),\(Y(j) = dp[j] + sum[j]^2\),转移变为:
\[dp[i] = sum[i]^2 + m + \min_{j < i} \left\{ Y(j) - sum[i] \cdot X(j) \right\}
\]
由于 \(sum[i]\) 单调递增(数列元素非负),且 \(X(j)\) 也单调递增,可用单调队列维护下凸包优化 DP。
示例程序:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int maxn = 5e5 + 5;
int n, c[maxn], m;
ll sum[maxn], dp[maxn];
int que[maxn], l, r;
ll X(int i) { return 2 * sum[i]; }
ll Y(int i) { return dp[i] + sum[i] * sum[i]; }
ll fz(int a, int b) { return Y(a) - Y(b); }
ll fm(int a, int b) { return X(a) - X(b); }
void solve() {
que[l = r = 0] = 0;
for (int i = 1; i <= n; i++) {
while (l < r && fz(que[l], que[l+1]) <= sum[i] * fm(que[l], que[l+1]))
l++;
int j = que[l];
dp[i] = dp[j] + (sum[i] - sum[j]) * (sum[i] - sum[j]) + m;
while (l < r && fz(que[r-1], que[r]) * fm(que[r], i) >= fz(que[r], i) * fm(que[r-1], que[r]))
r--;
que[++r] = i;
}
printf("%lld\n", dp[n]);
}
int main() {
while (~scanf("%d%d", &n, &m)) {
for (int i = 1; i <= n; i++) {
scanf("%d", c+i);
sum[i] = sum[i-1] + c[i];
}
solve();
}
return 0;
}
浙公网安备 33010602011771号