P3648 [APIO2014] 序列分割
//URL: /* 你正在玩一个关于长度为 nn 的非负整数序列的游戏。这个游戏中你需要把序列分成 k+1k+1 个非空的块。为了得到 k+1k+1 块,你需要重复下面的操作 kk 次: 选择一个有超过一个元素的块(初始时你只有一块,即整个序列) 选择两个相邻元素把这个块从中间分开,得到两个非空的块。 每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分 在确定切断位置时 顺序不影响最后的答案 abc显然 数学归纳法 f[i][j] 前i个数 分成j份 max f[i][j]=max(f[i][j],f[l][j-1]+sum[l]*(sum[i]-sum[l])) 若i<j 且j更优 则 (gj-sj^2)-(gk-sk^2)/sk-sj <=si 因为计算s时是正序 所以维护递增的序列即可 若i<j且 i更优 则 fi-si^2-(fj-sj^2)/-si-(-sj) >si 仍然正序 维护递增 但是点(-si,fi-si^2) :::单调队列只是判断最优解 的一种方式而已 :::若si==sj 即aj==0 直接返回-inf不影响最后结果 否则难以选择return值 */ /* 7 3 4 1 3 4 0 2 3 108 1 3 5 */ #include<cstdio> #include<iostream> #include<algorithm> #include<cmath> #include<string.h> #include<queue> #include<vector> #include<bits/stdc++.h> typedef long long ll; #define ddd printf("-----------------------\n"); using namespace std; const int N=1e5+5,M=205; int n,k,a[N],q[N],pre[N][M],ans[N]; long long s[N],f[N],g[N]; double slope(int i,int j) { if(s[i]==s[j]) return -1e18; return 1.0*((g[i]-s[i]*s[i])-(g[j]-s[j]*s[j]))/(s[j]-s[i]); } int main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;++i) scanf("%d",&a[i]),s[i]=s[i-1]+a[i]; for(int j=1;j<=k;++j) { int l=1,r=0; q[++r]=0; for(int i=1;i<=n;++i) { while(l<r&&slope(q[l],q[l+1])<=s[i]) ++l; f[i]=g[q[l]]+s[q[l]]*(s[i]-s[q[l]]); pre[i][j]=q[l]; while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) --r; q[++r]=i; } memcpy(g,f,sizeof(f)); } printf("%lld\n",f[n]); for(int x=n,i=k;i>=1;--i) x=pre[x][i],ans[i]=x; //for(int x=n,i=k;i>=1;--i) x=pre[x][i],printf("%d%c",x," \n"[i==1]); for(int i=1;i<=k;i++) cout<<ans[i]<<" "; cout<<'\n'; return 0; }