bzoj4709 [JSOI2011]柠檬

Captain_Paul

2018-10-08 21:38:45

Personal

题意:给出一个长度为$n$的序列,需要把它分成任意多段    对于每一段,需要指定一个数$x$,如果这一段中有$k$个$x$,那么收益为$xk^2$    最大化收益总和 ------------ **决策单调性优化**$DP$ 贴上原文链接:[orz](https://www.cnblogs.com/halfrot/p/7440794.html) 首先可以发现选出的**每一个区间的开头结尾一定是相同的数** 如果不是相同的数,那么这个数就可以放到其他区间中贡献答案,不影响这个区间 并且我们只关注如何划分这个序列,**选择区间的顺序不会产生影响** ------------ 设$f[i]$表示到第$i$个数的最大收益 有转移方程:$f[i]=max\left\{f[j-1]+a[i]\times(s[i]-s[j]+1)^2\right\},(a[j]==a[i])$ 其中$s[i]$表示到第$i$个位置$a[i]$这个数的出现次数 这样转移是$n^2$的,考虑优化 ------------ 发现$s[i]$是单调上升的,**$(s[i]-s[j]+1)^2$也是单调上升的,并且增长越来越快** 所以如果存在$k<j$且$k$比$j$更优,则$k$一直比$j$更优 于是对每一个$a[i]$**用单调栈维护**,当栈顶的第二个元素比第一个元素更优时,弹出栈顶元素 ------------ 这样产生了一个问题:如果第二个元素不优于第一个,但第三个元素更优怎么办? 对于任意的$j1<j2<i1<i2$,如果$j1$超过$i1$的时间小于$j2$超过$i1$的时间,那么$j1$超过$i2$也早于$j2$超过$i2$ 可以**二分**求出任意一个$j$超过$k$的时间 在将$i$入栈前,先判断第二个元素超过$i$的时间是否小于第一个元素超过$i$的时间,如果是就弹出栈顶,否则将$i$入栈 这样可以保证每个元素超过上一个元素的时间也是单调的 ``` #include<cstdio> #include<cstring> #include<cctype> #include<vector> #include<algorithm> #define reg register using namespace std; typedef long long ll; const int N=1e5+5,M=1e4+5; int n,a[N],cnt[M],s[N]; ll f[N]; vector<int>p[M]; inline int read() { int x=0,w=1; char c=getchar(); while (!isdigit(c)&&c!='-') c=getchar(); if (c=='-') c=getchar(),w=-1; while (isdigit(c)) { x=(x<<1)+(x<<3)+c-'0'; c=getchar(); } return x*w; } inline ll calc(int x,int y){return f[x-1]+1ll*a[x]*y*y;} inline int getpos(int x,int y) { int l=1,r=n,ans=n+1; while (l<=r) { int mid=(l+r)>>1; if (calc(x,mid-s[x]+1)>=calc(y,mid-s[y]+1)) ans=mid,r=mid-1; else l=mid+1; } return ans; } int main() { n=read(); for (reg int i=1;i<=n;i++) { s[i]=++cnt[a[i]=read()]; while (p[a[i]].size()>1&&getpos(p[a[i]][p[a[i]].size()-2],p[a[i]][p[a[i]].size()-1])<=getpos(p[a[i]][p[a[i]].size()-1],i)) p[a[i]].pop_back(); p[a[i]].push_back(i); while (p[a[i]].size()>1&&getpos(p[a[i]][p[a[i]].size()-2],p[a[i]][p[a[i]].size()-1])<=s[i]) p[a[i]].pop_back(); f[i]=calc(p[a[i]][p[a[i]].size()-1],s[i]-s[p[a[i]][p[a[i]].size()-1]]+1); } printf("%lld\n",f[n]); return 0; } ```