洛谷3648 BZOJ3675 APIO2014 序列分割 dp 斜率优化

版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/88390604

题目链接

题意:
给你一个长度为 n n 的序列,你要把它切 k k 次分为 k + 1 k+1 段,每次切开一块之后对答案的贡献是新的两个块的权值和的乘积。你现在要输出切 k k 次后最大的权值和,洛谷上还要求输出分段的位置。

题解:
我们化一下式子会发现,你确定了所有要切的位置之后,我们会发现,切这些位置的先后顺序对答案没有影响。证明的话好像可以用个类似分配律的东西化一下吧。

我们考虑dp,我们设 d p [ i ] [ k ] dp[i][k] 表示前 i i 个位置,切了 k k 次,并且第 k k 次切的位置是 i i 的最大答案。我们有 d p [ i ] [ k ] = m a x ( d p [ j ] [ k 1 ] + p r e [ j ] ( p r e [ i ] p r e [ j ] ) ) dp[i][k]=max(dp[j][k-1]+pre[j]*(pre[i]-pre[j])) 。其中 p r e [ i ] pre[i] 表示前 i i 个数的权值前缀和,这个式子理解一下的话,可以看作虽然是从前向后转移,但是切的时候先切后面位置再切前面位置的,然后因为切的顺序与答案无关,我们任意的顺序都是等价的,这个顺序比较好算答案,就用了这个顺序。

然后我们发现前面那个式子可以斜率优化,再加上一个滚动数组,我们就可以时间空间复杂度都是 O ( n ) O(n) 的了。然后至于洛谷的输出方案的话,可能就记录一下每个状态转以来的上一个位置在哪就可以了,但是空间是 O ( n k ) O(nk) 的了。

代码:

#include <bits/stdc++.h>
using namespace std;

int n,k,q[2000010],l,r,opt,ji[210][1000010],gg;
long long a[1000010],dp[1000010][2],s[1000010],ans;
inline int read()
{
	int x=0;
	char s=getchar();
	while(s>'9'||s<'0')
	s=getchar();
	while(s>='0'&&s<='9')
	{
		x=x*10+s-'0';
		s=getchar();
	}
	return x;
}
inline double slop(int x,int y)
{
	if(s[x]==s[y])
	return -1e9;
	return (double)(s[y]*s[y]-dp[y][opt^1]-(s[x]*s[x]-dp[x][opt^1]))/(double)(s[y]-s[x]);
}
int main()
{
	n=read();
	k=read();
	for(int i=1;i<=n;++i)
	a[i]=read();
	for(int i=1;i<=n;++i)
	s[i]=s[i-1]+a[i];
	for(int i=1;i<=k;++i)
	{
		l=0;
		r=0;
		opt=i&1;
		for(int j=1;j<=n;++j)
		{
			while(l<r&&slop(q[l+1],q[l])<s[j])
			++l;
			ji[i][j]=q[l];
			dp[j][opt]=dp[q[l]][opt^1]+s[j]*s[q[l]]-s[q[l]]*s[q[l]];
			while(l<r&&slop(q[r-1],q[r])>slop(q[r],j))
			--r;
			q[++r]=j;
		}
	}
	for(int i=1;i<=n;++i)
	{
		if(ans<dp[i][opt])
		{
			ans=dp[i][opt];
			gg=i;
		}
	}
	printf("%lld\n",ans);
	for(int i=k;i>=1;--i)
	{
		gg=ji[i][gg];
		printf("%d ",gg);		
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/88390604