1. 程式人生 > 實用技巧 >HDU - 3507 Print Article(斜率DP)

HDU - 3507 Print Article(斜率DP)

題意:給出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;
}