Print Article HDU - 3507 dp 斜率优化

题解

dp求解,f[i]表示前i项的最小代价,s为前缀和。
转移方程f[i] = min(f[i], f[j] + (s[i] - s[j])^2 + M),dp复杂度O(N^2),使用斜率优化。

设f[i]转移从j比k好(k < j < i) 得斜率方程
f[j] + (s[i] - s[j])^2 + M < f[k] + (s[i] - s[k])^2 + M 约去相同项 jk项移至左测 i和常移至右侧得
((f[j] + s[j]^2) - (f[k] + s[k]^2)) / (s[j] - s[k]) < 2s[i]
令y = f[j]+s[j]^2, x = s[j] 则可以表示为点(y, x)计算斜率 表示为
(yj - yk) / (xj - xk) < 2s[i] 为了精度改乘法 (yj - yk) < 2s[i] * (xj - xk) 注意符号

当斜率小于2s[i]时 可以直接淘汰j点
证: f[i]从j转移比k好 则f[t]也是从j转移比k好(k < j < i < t) 令s[t] = s[i] + v得
f[j] + (s[i] + v - s[j])^2 + M < f[k] + (s[i] + v - s[k])^2 + M
(s[i] + v - s[j])^2 < (s[i] + v - s[k])^2
-2(s[i] + v)s[j] < -2(s[i] + v)s[k]
s[j] > s[k]得证

AC代码

#include <stdio.h>
#include <bits/stdc++.h>
#define fst first
#define sed second
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int N = 5e5 + 10;
ll f[N], s[N]; //f[i]前i项的最小代价 s[i]前i项前缀和
int ls[N], head, tail; //维护斜率递增上凸壳
/*
转移方程f[i] = min(f[i], f[j] + (s[i] - s[j])^2 + M)
设f[i]转移从j比k好(k < j < i) 得斜率方程
f[j] + (s[i] - s[j])^2 + M < f[k] + (s[i] - s[k])^2 + M 约去相同项 jk项移至左测 i和常移至右侧得
((f[j] + s[j]^2) - (f[k] + s[k]^2)) / (s[j] - s[k]) < 2s[i]
令y = f[j]+s[j]^2, x = s[j] 则可以表示为点(y, x)计算斜率 表示为
(yj - yk) / (xj - xk) < 2s[i] 为了精度改乘法 (yj - yk) < 2s[i] * (xj - xk) 注意符号
*/
inline ll up(int k, int j) //(yj - yk) 注意传入时jk顺序!
{
	//ll res = f[j] + s[j] * s[j] - f[k] - s[k] * s[k];
	return f[j] + s[j] * s[j] - f[k] - s[k] * s[k];
}
inline ll down(int k, int j) //(xj - xk)
{
	//ll res = s[j] - s[k];
	return s[j] - s[k];
}
int main()
{
#ifdef LOCAL
	freopen("C:/input.txt", "r", stdin);
#endif
	int n, m;
	while (cin >> n >> m)
	{
		head = tail = 0;
		memset(f, 0, sizeof(f));
		memset(s, 0, sizeof(s));
		for (int i = 1; i <= n; ++i)
			scanf("%lld", &s[i]), s[i] += s[i - 1];
		ls[tail++] = 0; //可以通过0转移
		for (int i = 1; i <= n; ++i)
		{
			/*
			证: f[i]从j转移比k好 则f[t]也是从j转移比k好(k < j < i < t) 令s[t] = s[i] + v得
			f[j] + (s[i] + v - s[j])^2 + M < f[k] + (s[i] + v - s[k])^2 + M
			(s[i] + v - s[j])^2 < (s[i] + v - s[k])^2
			-2(s[i] + v)s[j] < -2(s[i] + v)s[k]
			s[j] > s[k]得证 所以当斜率小于2s[i]时 可以直接淘汰j点(无法证得使用二分求解)
			*/
			while (head + 1 < tail && up(ls[head], ls[head + 1]) <= 2 * s[i] * down(ls[head], ls[head + 1])) //改乘法后注意调整不等号
				head++; //淘汰表头
			int j = ls[head]; //通过表头转移
			f[i] = f[j] + (s[i] - s[j]) * (s[i] - s[j]) + m;
			/*
			设点jk斜率为g[j,k], 点ij斜率为g[i,j], 设2s[i]为e (k < j < i)
			若e > g[j,k] > g[i,j] 则 j比k优 i比j优
			若g[j,k] > e > g[i,j] 则 k比j优 i比j优
			若g[j,k] > g[i,j] > e 则 k比j优 j比i优
			无论如何都不会选j作为转移点 所以直接淘汰j
			*/
			while (head + 1 < tail && up(ls[tail - 2], ls[tail - 1]) * down(ls[tail - 1], i) >= up(ls[tail - 1], i) * down(ls[tail - 2], ls[tail - 1])) //改乘
				tail--; //淘汰表尾
			ls[tail++] = i; //插入
		}
		printf("%lld\n", f[n]);
	}

	return 0;
}

猜你喜欢

转载自blog.csdn.net/CaprYang/article/details/88346718