【BZOJ3675】【APIO2014】序列分割

题目:

题目在这里

思路:

这题的DP不难, 但不要被题目描述迷惑了

状态转移方程:

\[f_i = min \{ g_j + (sum_i - sum_j) * sum_j \} \]

(用了滚动数组, f是当前处理的, g是上一次得出的)

可以用斜率优化

\[g_j + (sum_i - sum_j) * sum_j >= g_k + (sum_i -sum_k) * sum_k \]

\[g_j - g_k - {sum_j}^2 + {sum_k}^2 >= sum_i * (sum_k - sum_j) \]

\[{g_j - g_k - {sum_j}^2 + {sum_k}^2 \over (sum_k - sum_j)} >= sum_i \]

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 100010;

inline long long sqr(long long x) { return x * x; }

long long a[N];
long long sum[N];

long long g[N], f[N];
int Q[N], hd, tl;
double calc(int j, int k) { return (double)(g[j]-g[k]+sqr(sum[k])-sqr(sum[j])) / (double)(sum[k]-sum[j]); }

int main()
{	int n, m;
	scanf("%d %d", &n, &m);
	for(int i=1; i<=n; i++)
		scanf("%lld", &a[i]);
	int tot = 0;
	for(int i=1; i<=n; i++)
		if(a[i] != 0) a[++tot] = a[i];
	n = tot;
	for(int i=1; i<=n; i++)
		sum[i] = sum[i-1] + a[i];
	for(int i=1; i<=m; i++)
	{	hd = tl = 0;
		for(int j=i; j<=n; j++)
		{	while(hd < tl-1 && calc(Q[hd], Q[hd+1]) <= sum[j]) hd++;
			f[j] = g[Q[hd]] + sum[Q[hd]] * (sum[j]-sum[Q[hd]]);
			while(hd < tl-1 && calc(Q[tl-1], j) <= calc(Q[tl-2], Q[tl-1])) tl--;
			Q[tl++] = j;
		}
		for(int j=i; j<=n; j++) swap(f[j], g[j]);
	}
	printf("%lld\n", g[n]);
	return 0;
}
posted @ 2018-03-22 16:41  EZ_WYC  阅读(136)  评论(0编辑  收藏  举报