【传送门:BZOJ3675】
简要题意:
一开始给出n个数的一段序列,可以分割k次,每次只能分割一段序列,一段序列被分割后就变成两个序列,每次分割的价值为分割的位置左边的数的和乘右边的数的和
求出最大价值
题解:
DP+斜率优化
首先来设f[i][k]为前i个数,分割k次得到的最大价值
接下来。。。
方程。。。。
方程。。。
方程。。
方程。
需要玄学思考
其实这里用了乘法分配律,证明分割的顺序是与答案无关的,只要割对地方就行了
太棒了这样就得到f[i][k]=max(f[j][k-1]+sum[j]*(sum[i]-sum[j]))
然后滚动数组f[i][now]=max(f[j][last]+sum[j]*(sum[i]-sum[j]))
再用斜率优化,美滋滋
设j1<j2<i
得到
f[j2][last]+sum[j2]*(sum[i]-sum[j2])>f[j1][last]+sum[j1]*(sum[i]-sum[j1])
f[j2][last]-f[j1][last]+sum[j1]^2-sum[j2]^2>(sum[j1]-sum[j2])*sum[i]
(f[j2][last]-f[j1][last]+sum[j1]^2-sum[j2]^2)/(sum[j1]-sum[j2])<sum[i]
因为sum[j1]-sum[j2]有可能等于0,所以在处理队列的时候,如果存在sum[j1]-sum[j2],要将j1给除掉,保留j2
注意加long long
参考代码:
#include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> using namespace std; typedef long long LL; //f[i][now]=max(f[j][last]+sum[j]*(sum[i]-sum[j])) //j1<j2<i //f[j2][last]+sum[j2]*(sum[i]-sum[j2])>f[j1][last]+sum[j1]*(sum[i]-sum[j1]) //f[j2][last]-f[j1][last]+sum[j1]^2-sum[j2]^2>(sum[j1]-sum[j2])*sum[i] //(f[j2][last]-f[j1][last]+sum[j1]^2-sum[j2]^2)/(sum[j1]-sum[j2])<sum[i] LL f[110000][2]; LL sum[110000]; int now=1,last=0; LL slop(int j1,int j2) { return (f[j2][last]-f[j1][last]-sum[j2]*sum[j2]+sum[j1]*sum[j1])/(sum[j1]-sum[j2]); } int list[110000]; int main() { int n,K; scanf("%d%d",&n,&K); sum[0]=0; for(int i=1;i<=n;i++) { int d; scanf("%d",&d); sum[i]=sum[i-1]+d; } memset(f,0,sizeof(f)); for(int k=1;k<=K;k++) { now^=1;last^=1; int head=1,tail=0;list[head]=0; for(int i=k;i<=n;i++) { while(head<tail&&(sum[list[head]]==sum[list[head]+1]||slop(list[head],list[head+1])<sum[i])) head++; int j=list[head]; f[i][now]=f[j][last]+sum[j]*(sum[i]-sum[j]); while(head<tail&&(sum[i]==sum[list[tail]]||slop(list[tail-1],list[tail])>slop(list[tail],i))) tail--; list[++tail]=i; } } printf("%lld ",f[n][now]); return 0; }