4709: [Jsoi2011]柠檬
Time Limit: 10 Sec Memory Limit: 128 MB
Description
Flute 很喜欢柠檬。它准备了一串用树枝串起来的贝壳,打算用一种魔法把贝壳变成柠檬。贝壳一共有 N (1 ≤ N
≤ 100,000) 只,按顺序串在树枝上。为了方便,我们从左到右给贝壳编号 1..N。每只贝壳的大小不一定相同,
贝壳 i 的大小为 si(1 ≤ si ≤10,000)。变柠檬的魔法要求,Flute 每次从树枝一端取下一小段连续的贝壳,并
选择一种贝壳的大小 s0。如果 这一小段贝壳中 大小为 s0 的贝壳有 t 只,那么魔法可以把这一小段贝壳变成 s
0t^2 只柠檬。Flute 可以取任意多次贝壳,直到树枝上的贝壳被全部取完。各个小段中,Flute 选择的贝壳大小 s
0 可以不同。而最终 Flute 得到的柠檬数,就是所有小段柠檬数的总和。Flute 想知道,它最多能用这一串贝壳
变出多少柠檬。请你帮忙解决这个问题。
Input
第 1 行:一个整数,表示 N。
第 2 .. N + 1 行:每行一个整数,第 i + 1 行表示 si。
Output
仅一个整数,表示 Flute 最多能得到的柠檬数。
Sample Input
5
2
2
5
2
3
Sample Output
21
HINT:Flute 先从左端取下 4 只贝壳,它们的大小为 2, 2, 5, 2。选择 s0 = 2,那么这一段
里有 3 只大小为 s0 的贝壳,通过魔法可以得到 2×3^2 = 18 只柠檬。再从右端取下最后一
只贝壳,通过魔法可以得到 1×3^1 = 3 只柠檬。总共可以得到 18 + 3 = 21 只柠檬。没有
比这更优的方案了。
题解
膜一波ZYFdalao
首先我们需要分析出一个性质:同一段被取下的贝壳,其第一个和最后一个的大小一定是一样的.
证明很容易.用反证法来看,如果有一段贝壳,开头和结尾不一样,显然把这两段分开施法更优
这样,我们又可以想出一个暴力:每一个节点的最大价值f[i],肯定是由前面某一个和他权值相同的点转移而来
设出现次数为s[i],权值为a[i]
显然我们可以写出暴力式子:f[i]=max{f[j-1]+a[i]*(s[i]-s[j]+1)2,a[i]==a[j]}
但是这样肯定会T的,所以我们考虑优化
对于两个决策点j1和j2,设j1<j2
如果一开始f[j1-1]+a[i]*(s[i]-s[j1]+1)2<f[j2-1]+a[i]*(s[i]-s[j2]+1)2
随着i不断向后,由于平方的增速很快,越靠前的j位置,越有可能反超,使得f[j1-1]+a[i‘]*(s[i‘]-s[j1]+1)2>f[j2-1]+a[i‘]*(s[i‘]-s[j2]+1)2
所以我们就不能再用单调队列了,因为如果我们删去了前面的某个点,它在后面可能会变得更优
于是我们考虑用单调栈维护这个东西(单调栈的实现方法随意,我个人使用了vector)
每次决策的时候,我们就用栈顶来更新
由于一个贝壳可以自己画为一段,所以应该在插入之后再计算f值
当上述"反超"的情况出现时,肯定是最先发生在栈顶和栈顶第二个元素
假设我们已经插入因此我们就看,第二个元素有"多快"超过栈顶
这个所谓的"速度"用经过a[i]的个数来体现,也就是说,看第二个元素经过几个a[i]超过栈顶,记为cnt1,如果cnt1<=s[i],我们就可以弹栈了.
但是,可能会出现一种情况:第三个元素超过栈顶,而第二个元素还没有超过栈顶
设j2<j1<i2<i1,如果j2,j1,i2三者满足这种情况,那么j2,j1,i1肯定更加满足这种情况,所以直接删掉j1就行
所以,在我们插入之前,可以先进行一波上述弹栈:如果发现第二个元素超过栈顶比栈顶超过i快,就弹栈;
所以最后的顺序是:排查->插入->计算
代码见下:
1 #include<cstdio> 2 #include<cstring> 3 #include<vector> 4 #include<deque> 5 #include<algorithm> 6 using namespace std; 7 typedef long long LL; 8 const int N=100010; 9 const int V=10010; 10 LL a[N],f[N]; 11 int id[N],cnt[V],n; 12 vector<int>q[V]; 13 inline int max(int a,int b){return a>b?a:b;} 14 inline LL calc(int x,int y){return f[x-1]+a[x]*y*y;} 15 inline int k(int x,int y) 16 { 17 int le=1,ri=n,ret=n+1; 18 while(le<=ri) 19 { 20 int mi=(le+ri)>>1; 21 if(calc(x,mi-id[x]+1)>=calc(y,mi-id[y]+1)) 22 ret=mi,ri=mi-1; 23 else le=mi+1; 24 } 25 return ret; 26 } 27 int main() 28 { 29 scanf("%d",&n);int o; 30 for(int i=1;i<=n;i++) 31 { 32 scanf("%lld",&a[i]);o=a[i]; 33 cnt[o]++,id[i]=cnt[o]; 34 while(q[o].size()>=2&&k(q[o][q[o].size()-2],q[o][q[o].size()-1])<=k(q[o][q[o].size()-1],i))q[o].pop_back(); 35 q[o].push_back(i); 36 while(q[o].size()>=2&&k(q[o][q[o].size()-2],q[o][q[o].size()-1])<=id[i])q[o].pop_back(); 37 f[i]=calc(q[o][q[o].size()-1],id[i]-id[q[o][q[o].size()-1]]+1); 38 } 39 printf("%lld",f[n]); 40 }
BZOJ4907