你正在玩一個關於長度爲 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