bzoj3675[Apio2014] 序列分割
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3675
题目大意:
将一个长度为n的非负整数序列分割成k+1个非空的子序列,每一次分割会得到一个得分,为这次所分割成的两部分中元素和的乘积,求最大的得分
=================================================
题解:
斜率优化
首先我们要发现,对于最终分割位置是一样的方案,无论过程分割的顺序是如何的,它们的得分都是每一部分中元素和的乘积,即为相等的,也就是说分割顺序不影响得分。这个很容易证的吧,懒得打了..随便化化就好了..吧~(听说是易证的我
设f[k][i]表示第k次的分割点为i.
那么方程就能写成f[k][i]=f[k-1][j]+(sum[i]-sum[j])*sum[j];
我的话是将它反过来想,YY成i+1~n是已经搞好的,在1~i这里切一下j,获得的收益就是(sum[i]-sum[j])*sum[j]。
于是,把这个化一下用斜率优化做就好啦~
但是然而,化出来:-sum[i]*sum[j]+f[k][i]=f[k-1][j]-sum[j]^2;
斜率
分母是可能为0的,所以要么就移项把sum[j2]-sum[j1]乘过去,要么就像黄学长那样把可能为0的删了(不想改动的我翻到黄学长的这种做法也就这样做了233)。
啊最后最后,如果你真的来开f[k][n]的数组是会爆空间的,要弄成滚动的..
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long LL; #define maxn 101000 int q[maxn],l,r,t; LL f[2][maxn],a[maxn],sum[maxn]; double Y(int j){return f[1-t][j]-sum[j]*sum[j];} double X(int j){return sum[j];} double slop(int j1,int j2){return (Y(j2)-Y(j1))/(X(j2)-X(j1));} int read() { char c;int tmp=0; c=getchar(); while (c<'0' || c>'9') c=getchar(); while (c>='0' && c<='9') { tmp*=10;tmp+=c-'0'; c=getchar(); }return tmp; } int main() { int ln,n,i,k;sum[0]=ln=0; n=read();k=read(); for (i=1;i<=n;i++) a[i]=read(); for (i=1;i<=n;i++) { if (a[i]!=0) a[++ln]=a[i]; sum[ln]=sum[ln-1]+a[ln]; }n=ln;t=0; memset(f,0,sizeof(f)); for (int ii=1;ii<=k;ii++) { l=1;r=0; for (i=ii;i<=n;i++) { while (l<r && slop(q[r-1],q[r])<slop(q[r],i-1)) r--; q[++r]=i-1; while (l<r && slop(q[l],q[l+1])>-sum[i]) l++; int j=q[l]; f[t][i]=f[1-t][j]+(sum[i]-sum[j])*sum[j]; }t=1-t; } printf("%lld\n",f[1-t][n]); return 0; }
啊我的代码,,巨慢orzorzorz。。。读优加了也就快了几百ms,然而比起我巨慢的总时间来说简直微不足道。(每次交我都觉得我在卡评测。。。