問題引入:
我們先來回想一下生成樹是如何定義的,生成樹就是用n - 1條邊將圖中的所有n個頂點都連通為一個連通分量,這樣的邊連成子樹稱為生成樹。
最小生成樹很明顯就是生成樹中權值最小的生成樹,那么我們即將要學的次小生成樹或者K小生成樹是怎么定義的呢,很明顯就是生成樹中權值第k小的生成樹。
下面給出劉老師書中對次小生成樹的定義,我是用自己的話描述的。
對於一個無向圖G(V, E),其定義了邊權為W(u, v),若T為他的一顆最小生成樹,那么我們假設存在一顆生成樹T1,不存在任意一顆G的生成樹T2滿足W(T) <= W(T2) < W(T1),那么我們就稱T1為
G的次小生成樹。
非嚴格次小生成樹的求解:
我們知道最小生成樹我們是通過Prim和Kruskal這樣的貪心算法求得的,那么次小生成樹我們只是對這兩種算法進行我們需要的修改就可以進行次小生成樹的求解。
我們很容易可以想到最小生成樹和次小生成樹應該是有聯系的,那么是如何聯系的呢?次小生成樹就是圖G的所有生成樹中權值第二小的生成樹,也就是說我們只需要替換最小生成樹的一條邊(u, v)
就可以得到次小生成樹,顯然這條邊肯定不可以屬於原最小生成樹,如果我們將一條不屬於原最小生成樹的邊(u, v)加入T,那么此時T中就形成了一個環,我們在環中選一個除(u, v)權值最大的邊進行刪除
(想一下為什么是選擇權值最大的那條邊),得到的樹依然是一顆圖G的生成樹,我們將所有邊逐個加入原最小生成樹T,得出並記錄所有的生成樹的權值T1,那么最后T1中最小的那個值即是次小生成樹的
權值。
下面只對與求解最小生成樹中不同的部分進行說明:
Prim:
我們知道Prim算法是以給定的任意點作為起始點運用一定的方法對所有點進行貪心處理,縮點從而生成一顆最小生成樹,那我們只需要用數組用來描述最小生成樹中每條邊的訪問情況以及最小
生成樹中每兩個頂點之間的最大邊權還需要保存最小生成樹中每個頂點的父親頂點,從而就可以方便用於計算次小生成樹。
具體操作:
初始化:初始化所有點( i )距離最小生成樹子樹的距離為cost[source][ i ],所有邊初始化為未訪問,所有頂點之間的最大邊權初始化為0。
加邊:每次加入一條安全邊(這里不對安全邊進行解釋,有不了解的可以查閱博主的上一篇博客),並將最小生成子樹中頂點之間的最大邊權進行更新,接着更新lowc即可。
求解次小生成樹:我們逐一枚舉出所有不屬於最小生成樹的邊(u, v),並且用w(u, v)來替代最大邊權和Max(u, v),怎么個替代法?
SecondMST = min(MST + w(u, v) - Max(u, v)) ((u, v) not belong to MST)。
OK?
參考代碼:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 using namespace std; 5 6 const int maxn = 1000 + 10, INF = 0x3f3f3f3f; 7 int n, m, lowc[maxn], pre[maxn], Max[maxn][maxn], cost[maxn][maxn]; 8 bool vis[maxn], used[maxn][maxn]; 9 10 int Prim() { 11 int ans = 0; 12 memset(vis, false, sizeof vis); 13 memset(Max, 0, sizeof Max); 14 memset(used, false, sizeof used); 15 vis[1] = true; 16 pre[1] = -1; 17 for(int i = 2; i <= n; i ++) { 18 lowc[i] = cost[1][i]; 19 pre[i] = 1; 20 } 21 lowc[1] = 0; 22 for(int i = 2; i <= n; i ++) { 23 int MIN = INF, p = -1; 24 for(int j = 1; j <= n; j ++) { 25 if(!vis[j] && MIN > lowc[j]) { 26 MIN = lowc[j]; 27 p = j; 28 } 29 } 30 if(MIN == INF) return -1; 31 ans += MIN; 32 vis[p] = true; 33 used[p][pre[p]] = used[pre[p]][p] = true; 34 for(int j = 1; j <= n; j ++) { 35 if(vis[j] && j != p) Max[j][p] = Max[p][j] = max(Max[j][pre[p]], lowc[p]); 36 if(!vis[j] && lowc[j] > cost[p][j]) { 37 lowc[j] = cost[p][j]; 38 pre[j] = p; 39 } 40 } 41 } 42 return ans; 43 } 44 45 int Second_Prim(int MST) { 46 int ans = INF; 47 for(int i = 1; i <= n; i ++) 48 for(int j = i + 1; j <= n; j ++) 49 if(!used[i][j] && cost[i][j] != INF) ans = min(ans, MST - Max[i][j] + cost[i][j]); 50 return ans; 51 } 52 53 int main() { 54 int t, a, b, c; 55 scanf("%d", &t); 56 while(t --) { 57 scanf("%d %d", &n, &m); 58 for(int i = 0; i <= n; i ++) 59 for(int j = 0; j <= n; j ++) 60 cost[i][j] = INF; 61 for(int i = 1; i <= m; i ++) { 62 scanf("%d %d %d", &a, &b, &c); 63 cost[a][b] = cost[b][a] = c; 64 } 65 int MST = Prim(); 66 int Second_MST = Second_Prim(MST); 67 printf("%d\n", Second_MST); 68 } 69 return 0; 70 }
Kruskal:
Kruskal算法是將圖G的所有邊進行排序,從小到大滿足邊的兩個頂點有一個不在subMST中就將其加入MST,在求解次小生成樹問題時我們也需要記錄MST中結點的連接情況,以及MST中兩個頂點
間的最大邊權。
具體操作:
初始化:初始化並查集,初始化在subMST中每個結點 i 直接或者間接相連的邊為i。
加邊:每次加入一條邊時,我們更新subMST中所有與u, v相連的結點間的最大邊權,接着將所有與結點v相連的邊都與結點u也連起來就行了(前提是在合並時head[ head[ v ] ] = head[ u ])。
求解次小生成樹:
SecondMST = min(MST + w(u, v) - Max(u, v)) ((u, v) not belong to MST)。滑稽.jpg
參考代碼:
1 #include <cstdio> 2 #include <cstring> 3 #include <vector> 4 #include <algorithm> 5 using namespace std; 6 7 const int maxn = 1000 + 10, maxe = 1000 * 1000 / 2 + 5, INF = 0x3f3f3f3f; 8 int n, m, pre[maxn], head[maxn], Max[maxn][maxn]; 9 struct Edge { 10 int u, v, w; 11 bool vis; 12 }edge[maxe]; 13 vector<int> G[maxn]; 14 15 bool cmp(const Edge &a, const Edge &b) { 16 return a.w < b.w; 17 } 18 19 void Init() { 20 for(int i = 1; i <= n; i ++) { 21 G[i].clear(); 22 G[i].push_back(i); 23 head[i] = i; 24 } 25 } 26 27 int Find(int x) { 28 if(head[x] == x) return x; 29 return head[x] = Find(head[x]); 30 } 31 32 int Kruskal() { 33 sort(edge + 1, edge + 1 + m, cmp); 34 Init(); 35 int ans = 0, cnt = 0; 36 for(int i = 1; i <= m; i ++) { 37 if(cnt == n - 1) break; 38 int fx = Find(edge[i].u), fy = Find(edge[i].v); 39 if(fx != fy) { 40 cnt ++; 41 edge[i].vis = true; 42 ans += edge[i].w; 43 int len_fx = G[fx].size(), len_fy = G[fy].size(); 44 for(int j = 0; j < len_fx; j ++) 45 for(int k = 0; k < len_fy; k ++) 46 Max[G[fx][j]][G[fy][k]] = Max[G[fy][k]][G[fx][j]] = edge[i].w; 47 head[fx] = fy; 48 for(int j = 0; j < len_fx; j ++) 49 G[fy].push_back(G[fx][j]); 50 } 51 } 52 return ans; 53 } 54 55 int Second_Kruskal(int MST) { 56 int ans = INF; 57 for(int i = 1; i <= m; i ++) 58 if(!edge[i].vis) 59 ans = min(ans, MST + edge[i].w - Max[edge[i].u][edge[i].v]); 60 return ans; 61 } 62 63 int main() { 64 int t; 65 scanf("%d", &t); 66 while(t --) { 67 scanf("%d %d", &n, &m); 68 for(int i = 1; i <= m; i ++) { 69 scanf("%d %d %d", &edge[i].u, &edge[i].v, &edge[i].w); 70 edge[i].vis = false; 71 } 72 int MST = Kruskal(); 73 int Second_MST = Second_Kruskal(MST); 74 printf("%d\n", Second_MST ); 75 } 76 return 0; 77 }