BZOJ4709 JSOI2011 柠檬
Description
$Flute$很喜欢柠檬。它准备了一串用树枝串起来的贝壳,打算用一种魔法把贝壳变成柠檬。贝壳一共有$N(1\leq N\leq 100000)$只,按顺序串在树枝上。为了方便,我们从左到右给贝壳编号 $1$..$N$。每只贝壳的大小不一定相同,贝壳 $i$的大小为 $s_i(1\leq s_i\leq 10,000)$。变柠檬的魔法要求,$Flute$ 每次从树枝一端取下一小段连续的贝壳,并选择一种贝壳的大小 $s_0$。如果 这一小段贝壳中 大小为$s_0$ 的贝壳有$t$只,那么魔法可以把这一小段贝壳变成$s_0t^2$ 只柠檬。$Flute$ 可以取任意多次贝壳,直到树枝上的贝壳被全部取完。各个小段中,$Flute$选择的贝壳大小$s_0 $可以不同。而最终$Flute$得到的柠檬数,就是所有小段柠檬数的总和。$Flute$想知道,它最多能用这一串贝壳变出多少柠檬。请你帮忙解决这个问题。
Input
第$1$行:一个整数,表示 $N$。
第$2$ ..$ N + 1 $行:每行一个整数,第 $i + 1$ 行表示$ s_i$。
Output
仅一个整数,表示 $Flute$ 最多能得到的柠檬数。
题目大意
给定一个序列,可以划分成任意的连续段,每一段选一个数,对于每一段,对答案的贡献是该段选的数乘以该数在这一段出现次数的平方,求所有答案之和的最大值。
有一个很显然的结论,对于最后选出来的每一段,一定满足该段两侧的数$=$该段选出来的数。
那么就可以列出$DP$方程,设$F_i$表示到第$i$位为止划分出的最大值,$C_i$表示从第$1$位开始到第$i$位$s_i$的出现次数。
$F_i=\max\{ F_{j-1}+s_i(C_i-C_j+1)^2\} (0<j\leq i,s_j=s_i)$。
展开后有$F_i-s_iC_i^2=\max\{ F_{j-1}-2s_iC_i(C_j-1)+s_j(C_j-1)^2\} (0<j\leq i,s_j=s_i)$。
暂且忽略$\max$,有$F_i-s_iC_i^2+2s_iC_i(C_j-1)=F_{j-1}+s_j(C_j-1)^2$
这个东西显然可以斜率优化。
设$X_j=2(C_j-1),Y_j=F_{j-1}+s_j(C_j-1)^2,b_i=F_i-s_iC_i^2,k_i=s_iC_i$
那么很显然就有$Y_j=k_iX_j+b_i$,而我们的目的是让截距$b_i$尽可能的较大。
这个式子在于意义在于斜率为让$k_i$的直线过$(X_j,Y_j)$且截距尽可能大(其中$k_i,X_j,Y_j$均为已知且可求)
我们发现只有上凸壳的点对答案的贡献才有意义,由于对于每一个$s_i$会单独考虑,那么直接维护上凸壳,因为原来函数是单峰的,就每次在上凸壳上二分出从前往后答案停止增加的位置,求答案即可
献上本机$AC$提交$RE$的代码(本地测$lysdy$数据)
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<vector> #include<cmath> #define LL long long #define M 200020 using namespace std; LL read(){ LL nm=0,fh=1; char cw=getchar(); for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh; for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0'); return nm*fh; } vector<LL> v[M]; LL n,m,p[M],c[M],cnt[M],F[M]; LL sq(LL x){return x*x;} LL X(LL i){return (c[i]-1)<<1;} LL Y(LL i){return F[i-1]+p[i]*sq((c[i]-1));} LL K(LL i){return p[i]*c[i];} bool check(LL t1,LL t2,LL t3){return (Y(t3)-Y(t2))*(X(t2)-X(t1))<(Y(t2)-Y(t1))*(X(t3)-X(t2));} LL getans(LL pos,LL from){return F[from-1]+p[pos]*sq(c[pos]-c[from]+1);} LL fd(LL i,LL tot){ LL l=0,r=tot-1,res=tot,md; for(md=((l+r)>>1);l<=r;md=((l+r)>>1)){ if(getans(i,v[p[i]][md])<=getans(i,v[p[i]][md+1])) l=md+1; else res=md,r=md-1; } return res; } int main(){ n=read(); for(LL i=1;i<=n;i++){ p[i]=read(),c[i]=++cnt[p[i]]; LL tt=v[p[i]].size()-1,now; while(tt>0) if(!check(v[p[i]][tt-1],v[p[i]][tt],i)) v[p[i]].pop_back(),tt--;else break; tt++,v[p[i]].push_back(i),now=fd(i,tt),F[i]=getans(i,v[p[i]][now]); } printf("%lld\n",F[n]); return 0; }