bzoj 1367 [ Baltic 2004 ] sequence —— 左偏树

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1367

好题啊!论文上的题;

论文上只给出了不下降序列的求法:

先考虑特殊情况,如果原序列上升,那么答案序列相同即可,如果下降,那么答案序列取中位数;

那么对于跌宕起伏的原序列,可以先一个一个加入元素,每次加入一个作为一个新区间,中位数是自己;

因为答案序列要不下降,所以当前区间的中位数比前一个区间大的时候就要合并,归纳可知(感性理解)整个区间的答案是它们的中位数;

论文中有严谨证明:https://wenku.baidu.com/view/20e9ff18964bcf84b9d57ba1.html

取中位数可以用一个大小是全体一半的大根堆,又要合并,所以就用可并堆;

那么求上升序列呢?

有个很巧妙的技巧,就是把原序列 t[i] = t[i] - i,然后对于这个序列求不下降序列;

那么得到答案序列后,把答案序列的每个元素都 + i,就得到一个上升序列,而差值的和还是不变的。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int const maxn=1e6+5;
int n,t[maxn],l[maxn],r[maxn],rt[maxn],siz[maxn],num[maxn];
int ls[maxn],rs[maxn],dis[maxn];
ll ans;
int abb(int x){return x>0?x:-x;}
int merge(int x,int y)
{
    if(!x||!y)return x+y;
    if(t[x]<t[y])swap(x,y);//维护大根堆 
    rs[x]=merge(rs[x],y);
    siz[x]=siz[ls[x]]+siz[rs[x]]+1;//+1
    if(dis[ls[x]]<dis[rs[x]])swap(ls[x],rs[x]);
    if(rs[x])dis[x]=dis[rs[x]]+1;
    else dis[x]=0;
    return x;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&t[i]),t[i]-=i;
    int nw=0;
    for(int i=1;i<=n;i++)
    {
        l[++nw]=r[nw]=i; rt[nw]=i;
        siz[rt[nw]]=num[nw]=1;  
        while(nw>1&&t[rt[nw-1]]>t[rt[nw]])
        {
            nw--; 
            num[nw]+=num[nw+1]; r[nw]=r[nw+1];
            rt[nw]=merge(rt[nw],rt[nw+1]);
            while(siz[rt[nw]]*2>num[nw]+1)//+1
                rt[nw]=merge(ls[rt[nw]],rs[rt[nw]]);
        }
    }
    for(int i=1;i<=nw;i++)
        for(int j=l[i];j<=r[i];j++)ans+=abb(t[j]-t[rt[i]]);
    printf("%lld\n",ans);
    return 0;
}

 

posted @ 2018-08-16 22:00  Zinn  阅读(188)  评论(0编辑  收藏  举报