正题
题目链接:https://www.luogu.com.cn/problem/P3648
题目大意
nnn个数字的序列,分割kkk次,每次的权值是左右两块数字的乘积。求最大权值和分割方案。
解题思路
显然分割顺序不会影响结果,一个分割方式的答案是每一块与其他块的乘积之和。
考虑dpdpdp,fi,jf_{i,j}fi,j表示第iii次分割,到第jjj个时的方案数,有转移
fi,j=max{fi−1,k+(sj−sk)∗sk}f_{i,j}=max\{f_{i-1,k}+(s_j-s_k)*s_k\}fi,j=max{fi−1,k+(sj−sk)∗sk}
考虑两个方案k′<kk'<kk′<k的优劣性
fi−1,k+sj∗sk−sk2>fi−1,k′+sj∗sk′−sk′2f_{i-1,k}+s_j*s_k-s_k^2>f_{i-1,k'}+s_j*s_{k'}-s_{k'}^2fi−1,k+sj∗sk−sk2>fi−1,k′+sj∗sk′−sk′2
fi−1,k+sj∗sk−fi−1,k′+sk′2>sj∗(sk′−sk)f_{i-1,k}+s_j*s_k-f_{i-1,k'}+s_{k'}^2>s_j*(s_{k'}-s_{k})fi−1,k+sj∗sk−fi−1,k′+sk′2>sj∗(sk′−sk)
fi−1,k+sj∗sk−fi−1,k′+sk′2sk′−sk<sj\frac{f_{i-1,k}+s_j*s_k-f_{i-1,k'}+s_{k'}^2}{s_{k'}-s_{k}}<s_jsk′−skfi−1,k+sj∗sk−fi−1,k′+sk′2<sj
因为sjs_jsj递增,所以单调队列维护一个上突壳即可
然后记录一下前驱输出方案
时间复杂度O(nk)O(nk)O(nk)
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
#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);
}