BZOJ4709 浅谈单调栈

来源:互联网 发布:肌肉型小腿如何瘦 知乎 编辑:程序博客网 时间:2024/06/07 22:45

这里写图片描述
世界真的很大
单调栈这东西其实挺玄学的。。
看起来很好懂其实应用还是很广的,认真想起来还是很烧脑。
以题为例
description

Flute 很喜欢柠檬。它准备了一串用树枝串起来的贝壳,打算用一种魔法把贝壳变成柠檬。贝壳一共有 N (1N100,000) 只,按顺序串在树枝上。为了方便,我们从左到右给贝壳编号 1..N。每只贝壳的大小不一定相同,贝壳 i 的大小为 si(1 ≤ si ≤10,000)。变柠檬的魔法要求,Flute 每次从树枝一端取下一小段连续的贝壳,并选择一种贝壳的大小 s0。如果 这一小段贝壳中 大小为 s0 的贝壳有 t 只,那么魔法可以把这一小段贝壳变成 s0t^2 只柠檬。Flute 可以取任意多次贝壳,直到树枝上的贝壳被全部取完。各个小段中,Flute 选择的贝壳大小 s0 可以不同。而最终 Flute 得到的柠檬数,就是所有小段柠檬数的总和。Flute 想知道,它最多能用这一串贝壳变出多少柠檬。请你帮忙解决这个问题。

input

1 行:一个整数,表示 N。第 2 .. N + 1 行:每行一个整数,第 i + 1 行表示 si。

output

仅一个整数,表示 Flute 最多能得到的柠檬数。

sample input

52 2 5 2 3

sample out

21//Flute 先从左端取下 4 只贝壳,它们的大小为 2, 2, 5, 2。选择 s0 = 2,那么这一段里有 3 只大小为 s0 的贝壳,通过魔法可以得到 2×3^2 = 18 只柠檬。再从右端取下最后一只贝壳,通过魔法可以得到 1×3^1 = 3 只柠檬。总共可以得到 18 + 3 = 21 只柠檬。没有比这更优的方案了。

首先是大神题解预热
发现:题目告诉你可以从左边取或者从右边取,而事实上就等价于让你把原序列划分成几段
在最优解中,每一段的两个端点一定相同(否则的话把某一端的一个单分一段可以更优)
那么我们可以列出DP式,f[i]=max(f[j-1]+a*(sa[i]-sa[j]+1)^2),其中i和j的颜色都为a, sa为统计颜色为a的贝壳有多少个的前缀和
直接DP的话是n^2的
我们发现,对于四个颜色都为a的位置i < j < k < l,在计算f[l]的时候i和j的贡献相比于计算f[k]的时候都增加了,而i增加的一定比j多(因为函数y=x^2上凸)
同时我们发现,对于i和j,在我们知道了f[i]和f[j]之后就能O(log n)计算出i变得比j优的位置:
由于i < j,所以总有一个位置起,j的值会不如i优,且随着位置的不断后移,j的值永远也不会比i优了,因为二次函数的斜率是逐渐增加的,一旦在某个位置i算出来的最大值大于j算出的最大值,而对于之后的计算,i又

int byd(int x,int y){    int lf=1,rg=n,rid=n+1;    while(lf<=rg)    {        int mid=(lf+rg)>>1;        if(cal(x,mid-s[x]+1)>=cal(y,mid-s[y]+1))        {            rid=mid;            rg=mid-1;        }        else         lf=mid+1;    }    return rid;}

那么我们对每种颜色维护一个单调栈,栈里是最有决策点,并关于前一个超过后一个的时间单调,每次求DP值的时候如果发现栈顶第二个元素比栈顶优了就弹栈:
维护的时候,首先在i入栈之前维护,看栈顶元素和栈顶-1的元素的最优决策点,栈顶的和当前的点的最优决策点的前后。
于是就O(n log n)解决了这个问题
完整代码:

#include<stdio.h>#include<vector>using namespace std;typedef long long dnt;int a[100010],c[10010],s[100010];int n,x;dnt f[100010];vector <int> st[10010];dnt cal(int x,int y){    return f[x-1]+ ( dnt ) a[x]*y*y;}int byd(int x,int y){    int lf=1,rg=n,rid=n+1;    while(lf<=rg)    {        int mid=(lf+rg)>>1;        if(cal(x,mid-s[x]+1)>=cal(y,mid-s[y]+1))        {            rid=mid;            rg=mid-1;        }        else         lf=mid+1;    }    return rid;}bool check_h(int x,int i){    return byd(st[x][st[x].size()-2],st[x][st[x].size()-1])<=byd(st[x][st[x].size()-1],i);}bool check_t(int x,int i){    return byd(st[x][st[x].size()-2],st[x][st[x].size()-1])<=s[i];}int main(){    scanf("%d",&n);    for(int i=1;i<=n;i++)    {        scanf("%d",&x);        a[i]=x;        s[i]=++c[x];        while(st[x].size()>=2&&check_h(x,i)) st[x].pop_back();        st[x].push_back(i);        while(st[x].size()>=2&&check_t(x,i)) st[x].pop_back();        f[i]=cal(st[x][st[x].size()-1],s[i]-s[st[x][st[x].size()-1]]+1);    }    printf("%lld",f[n]);}

嗯,就是这样。

原创粉丝点击