纠结了我好久。。。
众所周知,平衡树以维护树的平衡来达到高效,但一般来说维护平衡比较复杂,可保持不平衡就比较简单,于是,就有了左偏树这种不平衡的可并堆。
左偏树可以在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]:=0 end; 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] end end; 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 init end.