一个左偏树的经典应用

    以后写文章前先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

转载请注明出处

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值