你正在玩一个关于长度为 n 的非负整数序列的游戏。这个游戏中你需要把序列分成 k + 1 个非空的块。为了得到 k + 1 块,你需要重复下面的操作 k 次:
选择一个有超过一个元素的块(初始时你只有一块,即整个序列)
选择两个相邻元素把这个块从中间分开,得到两个非空的块。
每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分。
满足n<=100000,k<=200.
设有元素A B C D
(A+B)*(C+D)+(C*D)=AC+AD+BC+BD+CD=D(A+B+C)+C(B+A)
由此推广得到 元素切割顺序不影响得分情况
即可得到朴素DP 截止前i个元素被分成了j块的最大得分为f[i][j]
记录前缀和为s[i] f[i][j]=max(f[k][j-1]+(s[i]-s[j])*s[j] );
复杂度O(n²k)
开滚动数组优化空间。记录g[i]为分成j-1个块的最大得分。考虑斜率优化,推一推式子,转移时记录位置然后直接写就好了。
calc别整除。。别整除。。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll f[200005],q,sum[200005],n,a[200005],g[200005],dque[200005],ans;
inline ll read(){
char c=getchar();ll x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
int to[205][120005];
inline ll X(ll x){
return -sum[x];
}
inline ll Y(ll x){
return g[x]-sum[x]*sum[x];
}
// f[i][j]=f[k][j-1]+(sum[k])*(sum[i]-sum[k])
// f[i][j]-sum[k]*sum[i]=f[k][j-1]-sum[k]^2
// b kx = y
inline double calc(ll x,ll y){
if(X(y)-X(x)==0)return -1e18;
return (double)(Y(y)-Y(x))/(X(y)-X(x));
}
int sta[400005],top=0;
int main(){
scanf("%lld%lld",&n,&q);
for(int i=1;i<=n;i++){
a[i]=read();
}
for(int i=1;i<=n;i++){
sum[i]=sum[i-1]+a[i];
}
for(int k=1;k<=q;k++){
ll l=0,r=0;
dque[0]=0;
for(int i=k;i<=n;i++){
while(l<r&&calc(dque[l],dque[l+1])<=sum[i])l++;
int j=dque[l];
f[i]=g[j]+sum[j]*sum[i]-sum[j]*sum[j];
to[k][i]=j;
while(l<r&&calc(dque[r-1],dque[r])>=calc(dque[r],i))r--;
dque[++r]=i;
}
for(int i=k;i<=n;i++)g[i]=f[i];
}
printf("%lld\n",f[n]);
for(int i=to[q][n];i&&q;i=to[--q][i]){
sta[++top]=i;
}
for(int i=top;i>=1;i--){
printf("%d ",sta[i]);
}
return 0;
}
//10 5
//7 9 5 4 6 8 2 3 1