【最小树形图+带LAZY的可并堆】poj3164

题目大意:最小树形图;

最小树形图:给你一个带权有向图,选出一些边构成一颗有根树,并使得这棵树的边权之和最小;

 

大家都知道最小生成树的算法是prim和Kruskal,但是如果是有向图呢?萎了吧。。。这时候NB的刘朱算法就出现了,太IMBA了!

 

 

下面为基本流程.

最小树形图模型首先给定点集和一个根,若干条带权有向边,求从根出发的一个子图,边数为N-1,能从根到所有节点,并且边权和最小.

首先可以肯定的是,如果这个图从根走一遍后发现不连通,那么肯定无最小树形图.

否则每个仍在图中的点i记对于i点的最小权入边对应的入点为pre[i]

如果没有环,直接将所有cost[pre[i],i]相加记为ans

否则,缩环为点,记环为v,环上的任意点位vv,不在环上的任意点为u,将环上的边权和加入ans,改cost[v,u]=min{cost[vv,u]},cost[u,v]=min(cost[u,vv]-cost[pre[vv],vv])

再继续修改pre[i],并判断还有没有环.

这样改造后,对于任意一个环,最后加上某条入这条环的边,因为边权的改造,必将消去环上一条边,使得最小树形图中边位N-1且连通所有点.

from ly

 

 

这个算法的复杂度最坏是O(NM)的,后来灵机一动,发现可以用可并堆来实现O(logN)的时间查找最小值边权,至于修改操作,我很有创意地发明了个带LAZY标号的可并堆。

具体实现是用的斜堆,这样这道题的复杂度就通过数据结构优化成了O(N2logN)。但是由于LAZY标记的常数和斜堆本身带有2的常数以及我编丑了。。。

所以时间复杂度还是比较高的。

 

不过总的来说还是在POJ的前几名,不知道前面的大牛是靠漂亮的常数还是乱七八糟优化法才刷到那么快的速度。

 

代码:

 

{$inline on} program poj3164; const maxn=500; var a,b,l,r,root,v,fa:array[0..maxn*maxn] of longint; o:array[0..maxn] of boolean; g:array[0..maxn,0..maxn] of boolean; x,y,c,z:array[0..maxn*maxn] of real; i,j,k,n,m,t,e,tmp:longint; ans,pre:real; procedure put(i:longint);inline; begin c[l[i]]:=c[l[i]]-z[i];c[r[i]]:=c[r[i]]-z[i]; z[l[i]]:=z[l[i]]+z[i];z[r[i]]:=z[r[i]]+z[i]; z[i]:=0; end; function find(i:longint):longint;inline; begin if fa[i]<>i then fa[i]:=find(fa[i]);find:=fa[i] end; procedure merge(var x,y:longint);inline; begin if c[x]>c[y] then begin tmp:=x;x:=y;y:=tmp end; put(x); if y<>0 then begin merge(r[x],y); tmp:=r[x];r[x]:=l[x];l[x]:=tmp; end; end; procedure dfs(i:longint);inline;var j:longint; begin if o[i] then exit else inc(t); o[i]:=true; for j:=1 to n do if g[i,j] then dfs(j); end; function circle(i:longint):boolean;inline;var j,k:longint; begin fillchar(o,sizeof(o),false); t:=0;j:=i;o[1]:=true; repeat o[j]:=true; inc(t);v[t]:=j; j:=find(a[root[j]]); until o[j]; circle:=j=i; end; begin while not seekeof do begin fillchar(g,sizeof(g),false); fillchar(o,sizeof(o),false); fillchar(root,sizeof(root),0); fillchar(l,sizeof(l),0); fillchar(r,sizeof(r),0); fillchar(z,sizeof(z),0); readln(n,m);c[0]:=1e30;t:=0; for i:=1 to n do readln(x[i],y[i]); for i:=1 to m do begin readln(a[i],b[i]); g[a[i],b[i]]:=true; c[i]:=sqrt(sqr(x[a[i]]-x[b[i]])+sqr(y[a[i]]-y[b[i]])); end; dfs(1);ans:=0; if t<>n then writeln('poor snoopy') else begin for i:=1 to m do if a[i]<>b[i] then begin j:=i;merge(root[b[i]],j); end; for i:=1 to n do fa[i]:=i; for i:=2 to n do if fa[i]=i then while circle(i) do for j:=1 to t do begin e:=root[v[j]];z[e]:=z[e]+c[e]; ans:=ans+c[e];fa[v[j]]:=i;put(e); merge(r[e],l[e]);root[v[j]]:=r[e]; if j<>1 then merge(root[i],root[v[j]]); end; for i:=2 to n do if find(i)=i then ans:=ans+c[root[i]]; writeln(ans:0:2); end; end; end.  

你可能感兴趣的:(数据结构,c,优化,算法,function,merge)