題目大意:最小樹形圖;
最小樹形圖:給你一個帶權有向圖,選出一些邊構成一顆有根樹,並使得這棵樹的邊權之和最小;
大家都知道最小產生樹的演算法是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}<br />program poj3164; const maxn=500;<br />var<br /> a,b,l,r,root,v,fa:array[0..maxn*maxn] of longint;<br /> o:array[0..maxn] of boolean;<br /> g:array[0..maxn,0..maxn] of boolean;<br /> x,y,c,z:array[0..maxn*maxn] of real;<br /> i,j,k,n,m,t,e,tmp:longint;<br /> ans,pre:real;</p><p>procedure put(i:longint);inline;<br />begin<br /> c[l[i]]:=c[l[i]]-z[i];c[r[i]]:=c[r[i]]-z[i];<br /> z[l[i]]:=z[l[i]]+z[i];z[r[i]]:=z[r[i]]+z[i];<br /> z[i]:=0;<br />end;</p><p>function find(i:longint):longint;inline;<br />begin if fa[i]<>i then fa[i]:=find(fa[i]);find:=fa[i] end;</p><p>procedure merge(var x,y:longint);inline;<br />begin<br /> if c[x]>c[y] then begin tmp:=x;x:=y;y:=tmp end;<br /> put(x);<br /> if y<>0 then begin<br /> merge(r[x],y);<br /> tmp:=r[x];r[x]:=l[x];l[x]:=tmp;<br /> end;<br />end;</p><p>procedure dfs(i:longint);inline;var j:longint;<br />begin<br /> if o[i] then exit else inc(t);<br /> o[i]:=true;<br /> for j:=1 to n do if g[i,j] then dfs(j);<br />end;</p><p>function circle(i:longint):boolean;inline;var j,k:longint;<br />begin<br /> fillchar(o,sizeof(o),false);<br /> t:=0;j:=i;o[1]:=true;<br /> repeat<br /> o[j]:=true;<br /> inc(t);v[t]:=j;<br /> j:=find(a[root[j]]);<br /> until o[j];<br /> circle:=j=i;<br />end;</p><p>begin<br /> while not seekeof do begin<br /> fillchar(g,sizeof(g),false);<br /> fillchar(o,sizeof(o),false);<br /> fillchar(root,sizeof(root),0);<br /> fillchar(l,sizeof(l),0);<br /> fillchar(r,sizeof(r),0);<br /> fillchar(z,sizeof(z),0);<br /> readln(n,m);c[0]:=1e30;t:=0;<br /> for i:=1 to n do readln(x[i],y[i]);<br /> for i:=1 to m do begin<br /> readln(a[i],b[i]);<br /> g[a[i],b[i]]:=true;<br /> c[i]:=sqrt(sqr(x[a[i]]-x[b[i]])+sqr(y[a[i]]-y[b[i]]));<br /> end;<br /> dfs(1);ans:=0;<br /> if t<>n then writeln('poor snoopy') else begin<br /> for i:=1 to m do if a[i]<>b[i] then begin<br /> j:=i;merge(root[b[i]],j);<br /> end;<br /> for i:=1 to n do fa[i]:=i;<br /> for i:=2 to n do if fa[i]=i then<br /> while circle(i) do<br /> for j:=1 to t do begin<br /> e:=root[v[j]];z[e]:=z[e]+c[e];<br /> ans:=ans+c[e];fa[v[j]]:=i;put(e);<br /> merge(r[e],l[e]);root[v[j]]:=r[e];<br /> if j<>1 then merge(root[i],root[v[j]]);<br /> end;<br /> for i:=2 to n do if find(i)=i then ans:=ans+c[root[i]];<br /> writeln(ans:0:2);<br /> end;<br /> end;<br />end.