一个左偏树的经典应用

来源:互联网 发布:淘宝看店宝怎么下载 编辑:程序博客网 时间:2024/05/16 08:51

    以后写文章前先Orz Jerrydeng.

    之前讲过左偏树在dispatching中的应用,但是那还不够经典.左偏树最经典的应用,就是黄源河大牛05年论文中的那道例题.

    题目是这样的:

给一个序列a,要求你构造一个长度与a相同的非降序列b,使sigma(abs(ai-bi))最小

    黄源河对这个问题进行了细致的讲解,但是里面的证明略显学术了,一般的人(比如我)看了一半就不想看了.个人觉得与其看他的论文,还不如看他的ppt.

    总之,这道题的做法是这样的:维护一些连续的区间,每次加进第i个元素,将他作为一个独立的区间.每当最后一个区间与倒数第二个区间相冲突,就把两个区间合并,并把这两个区间中的b值都给为他们的中位数.

     先说明为什么要改成中位数:首先,由于最后两个区间有冲突,所以这两个区间一定要全部改成同一个数(设为k),修改这两段区间的代价就是sigma(abs(k-ai)),想象一个数轴,在每个ai上放一个物品,abs(k-ai)就是k与ai的距离.由常识我们可以知道,不会有方案比k为中位数时更优(证明自己yy).所以,如果两个区间相冲突,应该将区间中的数都改为中位数.

     这样,算法的大致流程就出来了:维护一个栈,设一个指针i,每次指针往后扫时将区间[i,i]入栈,然后检查栈顶元素是否跟他下面的元素冲突了,如果是就合并最上面两个区间,直到只剩一个元素或没有冲突.

     这个算法需要一个数据结构支持以下操作:

      1.查找一个区间的中位数.

      2.合并两个区间

     本来如果只是这样还不能用左偏树,只能用平衡树,但是,这道题有一个性质:某一个区间跟后面一个区间合并后中位数不会变大(想想为什么),于是,左偏树做法就呼之欲出了:对每个区间,维护一个大根堆,如果堆中元素超过区间长度的一半就删掉堆顶元素,这样最后的堆顶元素就是该区间的中位数.这个算法大致就是这样,顺便再Orz一下Jerrydeng.

Code:

program road;type        int=int64;var        i,j:longint;        k,m,p,n:int;        ll,rr,a,l,r,rt,s,dis,b:array[-1..100000]of int;{ll和rr是区间端点;rt是这个区间的root;l,r,dis就是左偏树里要用的三个数组;s是子树中的节点数}        ans:int;procedure swap(var x,y:int);var t:int;begin        t:=x;x:=y;y:=t;end;function union(x,y:int):int;begin        if(x=-1)or(y=-1)then exit(x+y+1);        if a[x]<a[y] then swap(x,y);        r[x]:=union(y,r[x]);        if dis[l[x]]<dis[r[x]]then swap(l[x],r[x]);        dis[x]:=dis[r[x]]+1;        s[x]:=s[l[x]]+s[r[x]]+1;        exit(x);end;procedure merge(x,y:int);var m,sum:int;begin        rt[x]:=union(rt[x],rt[y]);        ll[x]:=ll[x];rr[x]:=rr[y];        m:=rr[x]-ll[x]+1;        sum:=(m>>1)+(m and 1);        while s[rt[x]]>sum do rt[x]:=union(l[rt[x]],r[rt[x]]);end;begin        assign(input,'road.in');reset(input);        assign(output,'road.out');rewrite(output);        read(n);        s[-1]:=0;dis[-1]:=0;        p:=0;        for i:=1 to n do begin                read(a[i]);                inc(p);                l[i]:=-1;r[i]:=-1;                s[i]:=1;rt[p]:=i;                dis[i]:=dis[r[i]]+1;                ll[p]:=i;rr[p]:=i;                while(p<>1)and(a[rt[p]]<a[rt[p-1]])do begin                        merge(p-1,p);                        dec(p);                end;        end;        for i:=1 to p do                for j:=ll[i]to rr[i] do b[j]:=a[rt[i]];        ans:=0;        for i:=1 to n do inc(ans,abs(b[i]-a[i]));        write(ans);        close(input);close(output);end.



       以上的证明不是很严谨,有兴趣的同学最好还是看一下黄源河的论文.


BY QW

转载请注明出处