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;
}
posted @ 2026-09-17 16:53  quanjun  阅读(6)  评论(0)    收藏  举报