题目大意:给定一个N个点、M条边的无向带权图,边的权值均为正整数。若要使它变成非连通图,需要移除的边总权值最小是多少?
N≤500,图中不存在自环,但可能有重边(这里题意没交代清楚)。
Stoer-Wagner算法裸题。英文维基:https://en.wikipedia.org/wiki/Stoer%E2%80%93Wagner_algorithm
该算法的思想之一是:对于一个无向连通图,选定某两点s,t,以及该图的一个s-t割C,则“C是该图的全局最小割”是“C是s-t的最小割”的充分不必要条件。
也就是说,如果我们求得了某两点s-t的最小割C,那么全局最小割要么就是C,要么就是另一个让s,t保持连通的割。因此我们可以在得到s-t最小割之后,将这两个节点合并,然后继续求另外某两点的最小割。
合并节点的方法:对于s,t以外的每个节点u,设边(s,u)的权值为v1(如果该边不存在则v1=0),边(t,u)的权值为v2,那么合并后的新节点与u之间连一条边,权值为v1+v2(当然如果v1=0且v2=0则不用连这条边,反正权值为0的边可以随便删)。
该算法的流程为:
def MinCutPhase(a) 点集S ← a while |S| < 当前图中点的个数 记节点u与S中所有节点之间边的权值之和为w(u),选取不属于S且w(u)最大的节点u0 将u0加入S y = 最后一个加入S的节点 x = 倒数第二个加入S的节点 合并点x,y return 合并前的w(y) def MinCut() ans = +∞ while 图中点的个数 > 1 任选某个点a ans = min(ans, MinCutPhase(a)) return ans
可以证明在MinCutPhase过程中,w(y)就是x-y最小割的总权值(详见维基,说实话我也看不懂 ̄□ ̄)。
求w(u)的过程和最小生成树的Prim算法很类似。维护一个cost数组,对于每个新加入S的节点,更新与它相连的节点的cost值。
时间复杂度:单次MinCutPhase过程的复杂度与Prim算法相同。优化前为O(N^2),如果用邻接表+堆优化,可以将复杂度降到O(M + N logN)。
MinCutPhase过程共执行了O(N)次,因此总的复杂度为O(N^3)或O(MN + N^2 logN)。
O(N^3)的AC代码:
1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <climits> 5 6 using namespace std; 7 8 const int maxN = 500 + 5; 9 10 int dist[maxN][maxN]; //edge (u, v) does not exist if dist[u][v] == 0 11 int cost[maxN]; 12 bool erased[maxN]; //whether a vertex is erased after merging 13 bool vis[maxN]; //whether a vertex is visited during minCutPhase process 14 int N, M; 15 16 bool input() 17 { 18 if (scanf("%d%d", &N, &M) == EOF) 19 return false; 20 21 memset(dist, 0, sizeof(dist)); 22 for (int u, v, w, i = 0; i < M; i++) 23 { 24 scanf("%d%d%d", &u, &v, &w); 25 dist[u][v] = (dist[v][u] += w); 26 } 27 return true; 28 } 29 30 ///@brief: merge u and v, let dist[u][i] += dist[v][i], dist[v][i] = 0; (i.e. "erase" node v) 31 void mergeVertex(int u, int v) 32 { 33 for (int i = 0; i < N; i++) 34 { 35 dist[i][u] = (dist[u][i] += dist[v][i]); 36 dist[v][i] = dist[i][v] = 0; 37 } 38 erased[v] = true; 39 } 40 41 ///@brief: get the minimum s-t cut, and merge s and t 42 ///@param remN: number of remaining vertices 43 int minCutPhase(int remN) 44 { 45 memset(vis, 0, sizeof(vis)); 46 memset(cost, 0, sizeof(cost)); 47 48 int cur = static_cast<int>(find(erased, erased + N, false) - erased); //find a non-erased node 49 int pre = cur; 50 vis[cur] = true; 51 52 for (int i = 1; i < remN; i++) //repeat (remN - 1) times 53 { 54 for (int to = 0; to < N; to++) 55 cost[to] += dist[cur][to]; 56 57 int maxCost = 0; 58 int maxCostVertex = 0; 59 for (int j = 0; j < N; j++) 60 { 61 if (!vis[j] && cost[j] > maxCost) 62 { 63 maxCost = cost[j]; 64 maxCostVertex = j; 65 } 66 } 67 68 if (maxCost == 0) //not updated at all, indicating that the graph is not connected 69 return 0; 70 71 pre = cur; 72 cur = maxCostVertex; 73 vis[cur] = true; 74 } 75 76 mergeVertex(pre, cur); 77 return cost[cur]; 78 } 79 80 int minCut() 81 { 82 memset(erased, 0, sizeof(erased)); 83 84 int ans = INT_MAX; 85 for (int remN = N; remN > 1; --remN) 86 { 87 int t = minCutPhase(remN); 88 if (t == 0) 89 return 0; 90 ans = min(ans, t); 91 } 92 return ans; 93 } 94 95 int main() 96 { 97 while (input()) 98 printf("%d ", minCut()); 99 return 0; 100 }