BZOJ3675 [Apio2014]序列分割[斜率优化dp]
好久没写题解了惹,主要去省选玩了一趟之后就补文化课了,oi忘得差不多了。。
所以众所周知,这个人又来切水题了。
发现题中划分是无序的,不利于dp。是否可以从左到右依次划分?然后发现不管我过程中怎么划分,顺序是什么,只要最终划分的块都一样,那价值肯定一样,因为对于最终每一块,他在过程中每次被划开时,和被划开的另一大块中每一个最终的小块都产生了一次价值,所以最终一个划分价值就是各个小块的和相互的乘积的和,也就是$\sum\limits_{i=1}^{k} \sum\limits_{j=1}^{i-1} sum_i*sum_j$。
所以就可以dp,设划分$k$块,目前到$i$,当前划分出来的新的一块与先前每一块都产生一次价值,即可算作这一块与先前整块产生的价值,$f[k][i]=max\{f[k-1][j]+(sum[i]-sum[j])*sum[j]\}$。
然后拆开来就是一个斜截式,于是上斜率优化,由于$f$太大会爆空间,所以滚动一下。没了。
不知道我为什么常数巨大,可能是我太菜了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#define dbg(x) cerr<<#x<<" = "<<x<<endl
#define _dbg(x,y) cerr<<#x<<" = "<<x<<" "<<#y<<" = "<<y<<endl
using namespace std;
typedef long long ll;
template<typename T>inline char MIN(T&A,T B){return A>B?A=B,1:0;}
template<typename T>inline char MAX(T&A,T B){return A<B?A=B,1:0;}
template<typename T>inline T _min(T A,T B){return A<B?A:B;}
template<typename T>inline T _max(T A,T B){return A>B?A:B;}
template<typename T>inline T read(T&x){
x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1;
while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x;
}
const int N=100000+7,M=200+5;
ll f[2][N],sum[N];
int q[N];//int g[N][M];
int n,m,l,r;
inline ll Y(int j,int p){return f[p^1][j]-sum[j]*sum[j];}
inline ll X(int j){return sum[j];}
int main(){//freopen("test.in","r",stdin);//freopen("test.out","w",stdout);
read(n),read(m);++m;
for(register int i=1;i<=n;++i)read(sum[i]),sum[i]+=sum[i-1];
for(register int k=2,p=1;k<=m;++k,p^=1){
q[l=r=1]=k-1;memset(f[p],0,sizeof f[p]);
for(register int i=k;i<=n;++i){
while(l<r&&Y(q[l+1],p)-Y(q[l],p)>=-sum[i]*(X(q[l+1])-X(q[l])))++l;
f[p][i]=f[p^1][q[l]]+(sum[i]-sum[q[l]])*sum[q[l]];//g[i][k]=q[l];
while(l<r&&(Y(q[r],p)-Y(q[r-1],p))*(X(i)-X(q[r]))<=(Y(i,p)-Y(q[r],p))*(X(q[r])-X(q[r-1])))--r;
q[++r]=i;
}
}
printf("%lld\n",f[!(m&1)][n]);
// int tmp=g[n][m],k=m-1;
// while(tmp)printf("%d ",tmp),tmp=g[tmp][k--];
// puts("");
return 0;
}