定義:一個有向圖,存在從某個點開始的到達所有的的一個最小生成樹,則它就是最小樹形圖。
從早晨到現在一直在翻資料,終於理解了一點。朱-劉算法的大概過程如下:
1、找到除了root以為其他點的權值最小的入邊。用In[i]記錄
2、如果出現除了root以為存在其他孤立的點,則不存在最小樹形圖。
3、找到圖中所有的環,並對環進行縮點,重新編號。
4、更新其他點到環上的點的距離,如:
環中的點有(Vk1,Vk2,… ,Vki)總共i個,用縮成的點叫Vk替代,則在壓縮后的圖中,其他所有不在環中點v到Vk的距離定義如下:
gh[v][Vk]=min { gh[v][Vkj]-mincost[Vkj] } (1<=j<=i)
而Vk到v的距離為
gh[Vk][v]=min { gh[Vkj][v] } (1<=j<=i)
5、重復3,4知道沒有環為止。
如下圖所示:

算法過程很簡單,不過用到好多拍代碼的技巧。。。。在HH那找到一個不錯的模板 POJ 3164:
View Code
#include <iostream> #include <cstdio> #include <cmath> #include <vector> #include <cstring> #include <algorithm> #include <string> #include <set> #include <ctime> #include <queue> #include <map> #include <sstream> #define CL(arr, val) memset(arr, val, sizeof(arr)) #define REP(i, n) for((i) = 0; (i) < (n); ++(i)) #define FOR(i, l, h) for((i) = (l); (i) <= (h); ++(i)) #define FORD(i, h, l) for((i) = (h); (i) >= (l); --(i)) #define L(x) (x) << 1 #define R(x) (x) << 1 | 1 #define MID(l, r) (l + r) >> 1 #define Min(x, y) x < y ? x : y #define Max(x, y) x < y ? y : x #define E(x) (1 << (x)) const double eps = 1e-6; const double inf = ~0u>>1; typedef long long LL; using namespace std; const int N = 110; const int M = 10010; struct node { double x, y; } point[N]; struct edg { int u, v; double cost; } E[M]; double In[N]; int ID[N]; int vis[N]; int pre[N]; int NV, NE; double SQ(int u, int v) { return sqrt((point[u].x - point[v].x)*(point[u].x - point[v].x) + (point[u].y - point[v].y)*(point[u].y - point[v].y)); } double Directed_MST(int root) { double ret = 0; int i, u, v; while(true) { REP(i, NV) In[i] = inf; REP(i, NE) { //找最小入邊 u = E[i].u; v = E[i].v; if(E[i].cost < In[v] && u != v) { In[v] = E[i].cost; pre[v] = u; } } REP(i, NV) { //如果存在除root以外的孤立點,則不存在最小樹形圖 if(i == root) continue; //printf("%.3lf ", In[i]); if(In[i] == inf) return -1; } int cnt = 0; CL(ID, -1); CL(vis, -1); In[root] = 0; REP(i, NV) { //找環 ret += In[i]; int v = i; while(vis[v] != i && ID[v] == -1 && v != root) { vis[v] = i; v = pre[v]; } if(v != root && ID[v] == -1) { //重新標號 for(u = pre[v]; u != v; u = pre[u]) { ID[u] = cnt; } ID[v] = cnt++; } } if(cnt == 0) break; REP(i, NV) { if(ID[i] == -1) ID[i] = cnt++; //重新標號 } REP(i, NE) { //更新其他點到環的距離 v = E[i].v; E[i].u = ID[E[i].u]; E[i].v = ID[E[i].v]; if(E[i].u != E[i].v) { E[i].cost -= In[v]; } } NV = cnt; root = ID[root]; } return ret; } int main() { //freopen("data.in", "r", stdin); ... }
面說的是定根的情況,如果是不定根的情況我們可以虛擬一個根,讓虛擬根到每個節點的距離為圖上所有邊的權值之和加一。這樣找到最小樹形圖后再減掉所有邊的權值之和加一就可以了。
比如HUD 2121
View Code
#include <iostream> #include <cstdio> #include <cmath> #include <vector> #include <cstring> #include <algorithm> #include <string> #include <set> #include <ctime> #include <queue> #include <map> #include <sstream> #define CL(arr, val) memset(arr, val, sizeof(arr)) #define REP(i, n) for((i) = 0; (i) < (n); ++(i)) #define FOR(i, l, h) for((i) = (l); (i) <= (h); ++(i)) #define FORD(i, h, l) for((i) = (h); (i) >= (l); --(i)) #define L(x) (x) << 1 #define R(x) (x) << 1 | 1 #define MID(l, r) (l + r) >> 1 #define Min(x, y) x < y ? x : y #define Max(x, y) x < y ? y : x #define E(x) (1 << (x)) const int eps = 1e-6; const int inf = ~0u>>1; typedef long long LL; using namespace std; const int N = 1024; const int M = N*N; struct edg { int u, v; int cost; } E[M]; int In[N]; int ID[N]; int vis[N]; int pre[N]; int NV, NE; int Minroot; int Directed_MST(int root) { int ret = 0; int i, u, v; while(true) { REP(i, NV) In[i] = inf; REP(i, NE) { //找最小入邊 u = E[i].u; v = E[i].v; if(E[i].cost < In[v] && u != v) { In[v] = E[i].cost; if(u == root) Minroot = i; //不能直接等於v,因為會縮邊 pre[v] = u; } } REP(i, NV) { //如果存在除root以外的孤立點,則不存在最小樹形圖 if(i == root) continue; if(In[i] == inf) return -1; } int cnt = 0; CL(ID, -1); CL(vis, -1); In[root] = 0; REP(i, NV) { //找環 ret += In[i]; int v = i; while(vis[v] != i && ID[v] == -1 && v != root) { vis[v] = i; v = pre[v]; } if(v != root && ID[v] == -1) { //重新標號 for(u = pre[v]; u != v; u = pre[u]) { ID[u] = cnt; } ID[v] = cnt++; } } if(cnt == 0) break; REP(i, NV) { if(ID[i] == -1) ID[i] = cnt++; //重新標號 } REP(i, NE) { //更新其他點到環的距離 v = E[i].v; E[i].u = ID[E[i].u]; E[i].v = ID[E[i].v]; if(E[i].u != E[i].v) { E[i].cost -= In[v]; } } NV = cnt; root = ID[root]; } return ret; } int main() { //freopen("data.in", "r", stdin); int i, l, x; while(~scanf("%d%d", &NV, &NE)) { l = 0; x = NE; REP(i, NE) { scanf("%d%d%d", &E[i].u, &E[i].v, &E[i].cost); l += E[i].cost; } l++; REP(i, NV) { E[NE].u = NV; E[NE].v = i; E[NE].cost = l; NE++; } NV++; int ans = Directed_MST(NV-1); if(ans == -1 || ans >= 2*l) puts("impossible"); else printf("%d %d\n", ans - l, Minroot - x); cout << endl; } return 0; }
