可并堆 poj3016 不平衡的美

来源:互联网 发布:淘宝蛋刀必出bug 编辑:程序博客网 时间:2024/04/30 14:06

纠结了我好久。。。

众所周知,平衡树以维护树的平衡来达到高效,但一般来说维护平衡比较复杂,可保持不平衡就比较简单,于是,就有了左偏树这种不平衡的可并堆。

左偏树可以在2005年论文里找到,它不但实现简单,而且拥有一般数据结构所没有的合并操作,以及较优的复杂度(在此可以与只有天气良好,心情舒畅才能编的斐波那契堆,和合并复杂度未知的treap对比),可惜的是,应用面太小,经典问题也就那么几道,不过以后不知道有没有拓展。

poj 3016 是要求将一段长度为n的序列改造为有k段单调序列所需的最小费用。

很容易想到一个o(n^2*m)的动规方程f[i,j]:=min(f[i-1,k]+cost[k+1,j]),cost[i,j]为将[i,j]转为单调的最小费用,很明显,这就回到左偏树的经典解题——将一段区间改为非增或非降得最少费用上,两遍左偏树+贪心即可。


第一次打分割线


 把整个序列分成若干块,每一块都改成那一块的中位数,而且保证每个块的中位数不小于前一个块的,那么就用左偏树维护这几个块的中位数,用栈来维护这些块

算法流程:

S1:(假设前i-1个数的块已经分好了)先把第i个数以一个数新开个块

S2:检查栈顶top的块的中位数是不是比top-1的要小,如果是则转S3,否则继续做下一个数i+1直到n个数都做完

S3:合并top和top-1两个块,转S2

至于中位数怎么维护,其实是很简单的

一个块如果有n个数,你只保留较小的(n+1)div 2个就可以了,用左偏树做个最大堆,最大的元素就是中位数

上述算法在05黄源河论文里讲的更详细一点,你还可以参考一下那篇论文

本题要求个代价,那就还需要维护一些东西,论文里没有讲,其实也是很简单的

就每个左偏树维护4个东西

堆中元素个数now[i]、这些数的和s[i]、属于这个堆但被从堆中去掉的元素个数go[i]、这些数的和b[i]

最后ans=sigma每个块(该块中位数*now[i]-s[i]+b[i]-go[i]*中位数)

时间复杂度O(nlogn)  by syj


结束


由于ac这道题的人太少,大牛们一般又不喜欢写这种简单题的题解,所以我在单调转非降的地方纠结了老半天。

其实很简单,我们要使a[i-1]<a[i],可以转化为a[i-1]+1<=a[i]——>a[i-1]-(i-1)<=a[i]-i,令b[i]:=a[i]-i,即维护b[i]非降。

同理,a[i-1]>a[i]——>a[i-1]+(i-1)>=a[i]+i

维护左偏树时,不需在合并时维护过多信息,对我们有用的信息只在根处,因此只在根处维护就可以了。

至此,问题看似完美解决,但还有一个严重问题,分割线里的题解适用于非降,对于非升的话是有漏洞的。

我们会不停将大根堆里较大的删除,保证堆里的较小,之后合并的在非降时也只在较小时合并,而对于非升时有可能后面合并的比堆里去除的还大,计算费用时会出错,贪心也会出问题。

解决方法有三种:

1、改用小根堆。

2、从n到1求。

3、转化不等式a[i-1]+(i-1)>=a[i]+i——>-(a[i-1]+(i-1))<=-(a[i]+i)。

我用的是第三种。

第一次做ac人数在100以内的题,被各种各样的小问题纠结。

另外,syj说,如果时间不够,编一个左偏树可以应急当heap用

var n,m,top,e:longint;    l,r,d,s,sum,go,t,f:array[0..1000]of longint;    g:array[0..1000]of longint;    c:array[1..2,0..1000,0..1000]of longint;    cost:array[0..1000,0..1000]of longint;    a,b,st:array[1..1000]of longint;function merge(x,y:longint):longint;begin if x=0 then exit(y);if y=0 then exit(x); if b[x]<b[y] then begin  e:=x;x:=y;y:=e end; r[x]:=merge(r[x],y); if d[l[x]]<d[r[x]] then begin  e:=l[x];l[x]:=r[x];r[x]:=e end; d[x]:=d[r[x]]+1; exit(x)end;procedure origin;begin fillchar(f,sizeof(f),0);fillchar(st,sizeof(st),0);top:=0; fillchar(l,sizeof(l),0);fillchar(r,sizeof(r),0); fillchar(s,sizeof(s),0);fillchar(sum,sizeof(sum),0); fillchar(t,sizeof(t),0);fillchar(go,sizeof(go),0); fillchar(d,sizeof(d),0);d[0]:=-1;end;procedure ori(x:longint);begin inc(top);st[top]:=x;s[x]:=b[x];sum[x]:=1;t[x]:=0;go[x]:=0; l[x]:=0;r[x]:=0;d[x]:=0end;procedure work(x,y:longint);var i,ne,na,k,p:longint;begin origin; ori(x); for i:=x+1 to n do begin  ori(i);  while (top>1) and (b[st[top-1]]>b[st[top]]) do begin   k:=st[top-1];p:=st[top];   st[top-1]:=merge(st[top-1],st[top]);   dec(top);ne:=st[top];   s[ne]:=s[k]+s[p];sum[ne]:=sum[k]+sum[p];   t[ne]:=t[k]+t[p];go[ne]:=go[k]+go[p];   if (sum[ne]+go[ne]+1) >>1 < sum[ne] then begin    na:=merge(l[ne],r[ne]);    st[top]:=na;    s[na]:=s[ne]-b[ne];sum[na]:=sum[ne]-1;    t[na]:=t[ne]+b[ne];go[na]:=go[ne]+1   end  end;  ne:=st[top];  f[top]:=f[top-1]+(sum[ne]*b[ne]-s[ne]+t[ne]-go[ne]*b[ne]);  c[y,x,i]:=f[top] endend;procedure dp;var i,j,p:longint;begin fillchar(g,sizeof(g),61); for i:=1 to m do begin  g[0]:=0;  for j:=n downto 1 do   for p:=0 to j-1 do    if g[p]+cost[p+1,j]<g[j] then     g[j]:=g[p]+cost[p+1,j] end;end;function min(x,y:longint):longint;begin if x<y then exit(x) else exit(y)end;procedure init;var i,j:longint;begin readln(n,m); if n=0 then exit; fillchar(a,sizeof(a),0);fillchar(b,sizeof(b),0); for i:=1 to n do read(a[i]); if m>=n then begin writeln(0);exit end; fillchar(c,sizeof(c),0); for i:=1 to n do b[i]:=a[i]-i; for i:=1 to n do work(i,1); for i:=1 to n do b[i]:=-(a[i]+i); for i:=1 to n do work(i,2); fillchar(cost,sizeof(cost),0); for i:=1 to n do for j:=i+1 to n do cost[i,j]:=min(c[1,i,j],c[2,i,j]); dp; writeln(g[n])end;begin while not seekeof do initend.



原创粉丝点击