定義:
定根的最小樹形圖,就是給有向帶權圖中指定一個特殊的點root,求一棵以root爲根的有向生成樹T,並且T中所有邊的總權值最小。
算法實現過程:(定根)
朱-劉算法的大概過程如下:參考:hqd_acm的專欄 +幻影閣
首先消除自環,顯然自環不在最小樹形圖中。然後判定是否存在最小樹形圖,以根爲起點DFS一遍即可。判斷圖的連通性,若不連通直接無解,否則一定有解。
之後進行以下步驟。
設cost爲最小樹形圖總權值。
0.置cost=0。
1.求最短弧集合Ao (一條弧就是一條有向邊)
除源點外,爲所有其他節點Vi,找到一條以Vi爲終點的邊,把它加入到集合Ao中。
(加邊的方法:所有點到Vi的邊中權值最小的邊即爲該加入的邊,記prev[vi]爲該邊的起點,mincost[vi]爲該邊的權值)
2.檢查Ao中的邊是否會形成有向圈,有則到步驟3,無則到步驟4。(可利用並查集)
(判斷方法:利用prev數組,枚舉爲檢查過的點作爲搜索的起點,做類似DFS的操作)
3.將有向環縮成一個點。
假設環中的點有(Vk1,Vk2,… ,Vki)總共i個,用縮成的點叫Vk替代,則在壓縮後的圖中,其他所有不在環中點v到Vk的距離定義如下:
<1> gh[v][Vk]=min { gh[v][Vkj]-mincost[Vkj] } (1<=j<=i)而Vk到v的距離爲
<2> gh[Vk][v]=min { gh[Vkj][v] } (1<=j<=i)
同時注意更新prev[v]的值,即if(prev[v]==Vkj) prev[v]=Vk
另外cost=cost+mincost[Vkj] (1<=j<=i)
到步驟1.
<1>的理解:先假設環上所有邊均選上,若下次選擇某一條邊進入該環,則可以斷開進入點與進入點的前驅之間的邊,即斷開F[進入點],因爲之前已經把現有的最小值加到集合A0裏面了,所以現在只要加上多出來的那一部分,也就是min{a[p,Vi]-f[Vi]},所以等效爲直接把a[p,node]賦值爲min{a[p,Vi]-f[Vi]},有點像增廣的感覺,每次找到delta。
附一張圖片加以理解:
4.cost加上Ao的權值和即爲最小樹形圖總權值。
複雜度:有固定根
找環O(V),收縮O(E),總複雜度O(VE)。
模板:(POJ 3164 裸的最小樹形圖)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
using namespace std;
const int inf=0x3f3f3f3f;
int n,m;
struct point
{
int x,y;
}P[105];
struct Edge
{
int u,v;
double w;
}edge[10005];
int pre[105],color[105],mark[105];
int cnt_edge;
double in[105];
void AddEdge(int u,int v,double w)
{
edge[cnt_edge].u=u;
edge[cnt_edge].v=v;
edge[cnt_edge].w=w;
cnt_edge++;
}
double Cal(int x1, int y1,int x2, int y2)
{
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
double zhuliu(int root,int n,int m)
{
int i,j,u,v,cnt;
double w;
cnt=0;
double res=0;
while(1)
{
for(i=1;i<=n;i++)
in[i]=inf;
for(i=0;i<m;i++)
{
u=edge[i].u;
v=edge[i].v;
w=edge[i].w;
if(in[v]>w&&u!=v)
{
in[v]=w;
pre[v]=u;
}
}
for(i=1;i<=n;i++)
{
if(i!=root&&in[i]==inf)
return -1;
}
memset(mark,0,sizeof(mark));
memset(color,0,sizeof(color));
mark[root]=1;
in[root]=0;
cnt=0;
for(i=1;i<=n;i++)
{
res+=in[i];
v=i;
while(mark[v]!=i&&color[v]==0&&v!=root)
{
mark[v]=i;
v=pre[v];
}
if(v!=root&&color[v]==0)
{
cnt++;
for(u=pre[v];u!=v;u=pre[u])
color[u]=cnt;
color[v]=cnt;
}
}
if(cnt==0)
break;
for(i=1;i<=n;i++)
if(color[i]==0)
color[i]=++cnt;
for(i=0;i<m;i++)
{
v=edge[i].v;
edge[i].u=color[edge[i].u];
edge[i].v=color[edge[i].v];
if(edge[i].u!=edge[i].v)
edge[i].w-=in[v];
}
n=cnt;
root=color[root];
}
return res;
}
int main()
{
//freopen("in","r",stdin);
while(scanf("%d%d",&n,&m)!=EOF)
{
int i,j,f,t;
cnt_edge=0;
for(i=1;i<=n;i++)
scanf("%d %d",&P[i].x,&P[i].y);
for(i=0;i<m;i++)
{
scanf("%d%d",&f,&t);
if(f!=t)
AddEdge(f,t,Cal(P[f].x,P[f].y,P[t].x,P[t].y));
}
double ans=zhuliu(1,n,cnt_edge);
if(ans!=-1)
printf("%.2f\n",ans);
else printf("poor snoopy\n");
}
return 0;
}