P3648-[APIO2014]序列分割【斜率优化】

正题

题目链接:https://www.luogu.com.cn/problem/P3648


题目大意

n n n个数字的序列,分割 k k k次,每次的权值是左右两块数字的乘积。求最大权值和分割方案。


解题思路

显然分割顺序不会影响结果,一个分割方式的答案是每一块与其他块的乘积之和。

考虑 d p dp dp f i , j f_{i,j} fi,j表示第 i i i次分割,到第 j j j个时的方案数,有转移
f i , j = m a x { f i − 1 , k + ( s j − s k ) ∗ s k } f_{i,j}=max\{f_{i-1,k}+(s_j-s_k)*s_k\} fi,j=max{fi1,k+(sjsk)sk}
考虑两个方案 k ′ < k k'k<k的优劣性
f i − 1 , k + s j ∗ s k − s k 2 > f i − 1 , k ′ + s j ∗ s k ′ − s k ′ 2 f_{i-1,k}+s_j*s_k-s_k^2>f_{i-1,k'}+s_j*s_{k'}-s_{k'}^2 fi1,k+sjsksk2>fi1,k+sjsksk2
f i − 1 , k + s j ∗ s k − f i − 1 , k ′ + s k ′ 2 > s j ∗ ( s k ′ − s k ) f_{i-1,k}+s_j*s_k-f_{i-1,k'}+s_{k'}^2>s_j*(s_{k'}-s_{k}) fi1,k+sjskfi1,k+sk2>sj(sksk)
f i − 1 , k + s j ∗ s k − f i − 1 , k ′ + s k ′ 2 s k ′ − s k < s j \frac{f_{i-1,k}+s_j*s_k-f_{i-1,k'}+s_{k'}^2}{s_{k'}-s_{k}}skskfi1,k+sjskfi1,k+sk2<sj
因为 s j s_j sj递增,所以单调队列维护一个上突壳即可

然后记录一下前驱输出方案
时间复杂度 O ( n k ) O(nk) O(nk)


c o d e code code

#include
#include
#include
#define ll long long
using namespace std;
const ll N=110000;
ll n,k,q[N],fa[210][N],s[N],f[2][N];
double slope(ll z,ll x,ll y){
	if(s[y]==s[x])return -1e18;
	return (f[z][x]-f[z][y]-s[x]*s[x]+s[y]*s[y])/(double)(s[y]-s[x]);
}
int main()
{
	scanf("%lld%lld",&n,&k);
	for(ll i=1;i<=n;i++)
		scanf("%lld",&s[i]),s[i]+=s[i-1];
	memset(f,0,sizeof(f));
	for(ll i=1;i<=k;i++){
		ll head=1,tail=0;
		memset(f[i&1],0,sizeof(f[i&1]));
		for(ll j=1;j<=n;j++){
			while(head<tail&&slope(~i&1,q[head],q[head+1])<=s[j])head++;
			f[i&1][j]=0;
			if(head<=tail){
				ll x=q[head];fa[i][j]=x;
				f[i&1][j]=f[~i&1][x]+(s[j]-s[x])*s[x];
			}
			while(head<tail&&slope(~i&1,q[tail],q[tail-1])>=slope(~i&1,j,q[tail]))tail--;
			q[++tail]=j;
		}
	}
	printf("%lld\n",f[k&1][n]);
	for(ll i=k,w=fa[k][n];i;w=fa[--i][w])
		printf("%lld ",w);
}

你可能感兴趣的:(dp)