题意:给出N个单词,每个单词有个非负权值Ci,现在要将它们分成连续的若干段,每段的代价为此段单词的权值和的平方,还要加一个常数M。现在想求出一个最优方案,使得总费用之和最小。
分析:斜率DP优化,DP转移方程式为(f[i] = min(f[j] + (sum[i] - sum[j])^{2} + m), (0 <= j < i)),我们可以进行斜率DP优化,我们调整表达式为(f[j] + sum[j]^{2} = 2 * sum[i] * sum[j] - sum[i] ^ {2} + f[i] - m),假设(f[i] + sum[j] ^ {2})为(y),(2 * sum[j])为(x),那么(y = sum[i] * x + f[i] - sum[i]^2 - m),假设(sum[i]为k),那么(y = k * x + f[i] - sum[i]^{2} - m),对于二维平面上的点((2 * sum[1], f[1] + s[1]^{2}), (2 * sum[2], f[2] + s[2] ^ {2})dots),我们为了让(f[i])最小,从而使得截距(f[i] - sum[i]^{2} - m)最小,那么我们的直线要穿过平面上的凸包的下边界,然后用单调队列维护。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
using LL = long long;
const int N = 500005;
int a[N];
LL f[N];
LL sum[N];
int q[N];
LL get_y(int x)
{
return f[x] + sum[x] * sum[x];
}
int main()
{
int n, m;
while (scanf("%d%d", &n, &m) != EOF)
{
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for (int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + a[i];
int hh = 0, tt = 0;
q[0] = 0;
for (int i = 1; i <= n; ++i)
{
while (hh < tt && (get_y(q[hh + 1]) - get_y(q[hh])) <= (2 * sum[i] * (sum[q[hh + 1]] - sum[q[hh]]))) ++hh;
int k = q[hh];
f[i] = f[k] + (sum[i] - sum[k]) * (sum[i] - sum[k]) + m;
while (hh < tt && ((get_y(q[tt]) - get_y(q[tt - 1])) * 2 * (sum[i] - sum[q[tt]])) >= ((get_y(i) - get_y(q[tt])) * 2 * (sum[q[tt]] - sum[q[tt - 1]]))) --tt;
q[++tt] = i;
}
printf("%lld
", f[n]);
}
return 0;
}