题意:给出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\n", f[n]);
	}

	return 0;
}