Stoer-Wagner算法小结

介绍

这是个用来求全局最小割的算法,就是找一个割将无向图切成两份。

尽量写得清新易懂一点qwq,可能会有些啰嗦……

正题

算法建立在这个事实基础上:

对于两个点 x , y x,y x,y,去掉割边后要么在同一个连通块,要么不在同一个连通块。

咋一看像句废话,但实际上是个相当有用的性质。

假如在同一连通块内,对于一个在另一个连通块内的点 z z z x x x y y y z z z 的所有路径都应该被切断了。这意味着,我们其实可以将 x , y x,y x,y 放在一起考虑,即 将 x , y x,y x,y 合并成一个点。

那么算法大体流程就是:随便找两个点 x , y x,y x,y,求出他们的最小割更新答案,然后将他们合并在一起,一共合并 n − 1 n-1 n1 次。

剩下的问题就是如何快速随便找一个两点间的割了,Stoer-Wagner算法提供了一种 n 2 n^2 n2 的做法。

  • 假设目前剩余 n ′ n' n 个点,我们考虑按一种奇妙的顺序将其放入一个 A A A 序列,定义 w i = ∑ j = 1 ∣ A ∣ f ( i , A j ) w_i=\sum_{j=1}^{|A|}f(i,A_j) wi=j=1Af(i,Aj) f ( x , y ) f(x,y) f(x,y) 是指 x , y x,y x,y 间的边权,每次就将 w w w 最大的那个点丢到 A A A 序列的末尾。

  • 然后设 A A A 序列中最后两个点为 S , T S,T S,T,这意味着我们要求的就是最后两个点的割。设图为 G = ( V , E ) G=(V,E) G=(V,E),最后这个割为 C ∈ E C\in E CE

  • A A A 序列中 x x x 之前的点以及相关的所有边形成的子图中,包含的割边集合为 C x C_x Cx。定义点 A i A_i Ai 是active的,当且仅当满足去掉割边(即 C C C 集合)后, A i − 1 A_{i-1} Ai1 A i A_i Ai 不在同一个连通块内。接下来有一个结论:对于所有active的点 x x x,都有 w x ≤ W ( C x ) w_x\leq W(C_x) wxW(Cx),其中 W ( C x ) W(C_x) W(Cx) 表示 C x C_x Cx 这个割的容量。

  • 先假装这个结论是对的,那么对于点 T T T,他是 A A A 序列中最后一个点,意味着 C T = C C_T=C CT=C,而 C C C 这个割一定会将 S , T S,T S,T 分开,所以 T T T 一定是active的,那么有 w T ≤ W ( C ) w_T\leq W(C) wTW(C),也就是说, w T w_T wT 就是 S , T S,T S,T 这个割的下界。然而回想 w T w_T wT 的定义,发现 w T w_T wT 就是与 T T T 相连的所有边权值之和,如果断掉与 T T T 相连的所有边, S , T S,T S,T 是一定不连通的,这意味着 w T w_T wT 就是一个割的容量,并且是所有割中容量最小的那个,也就是我们要找的最小割。

也就是说,有了这个结论的话,我们就可以 O ( n 2 ) O(n^2) O(n2) 求出最小割的容量了,接下来就是如何证明这个结论:

使用归纳法,先考虑 A A A 序列中第一个active的点。对于这个点 x x x,不等式恰好取等号(即 w x = W ( C x ) w_x=W(C_x) wx=W(Cx)),因为在 x x x 之前的点都在同一个连通块内,要让 x x x 不与他们在同一个连通块内,就要割掉 x x x 与他们之间的所有边。

假设 x x x 点之前的所有active的点(包括 x x x)都满足该结论,并且 x x x 是active的,此时需要证明 x x x 往后第一个active的点 y y y 也是满足的。

对于 w y w_y wy,组成部分有 y y y 连向 x x x 之前的边,以及 y y y 连向 x x x y y y 这些点的边。对于第一部分,这部分显然不大于 w x w_x wx,因为它晚于 x x x 加入 A A A 序列。对于第二部分,由于 y y y 是active的,所以这些点都与他不在同一连通块, y y y 与他们之间的边全部都要割掉,所以这部分等于 W ( C y / C x ) W(C_y/C_x) W(Cy/Cx)

整理一下,我们有 w y ≤ w x + W ( C y / C x ) w_y\leq w_x+W(C_y/C_x) wywx+W(Cy/Cx) 以及 w x ≤ W ( C x ) w_x\leq W(C_x) wxW(Cx),即:
w y ≤ W ( C x ) + W ( C y / C x ) w y ≤ W ( C y ) \begin{aligned} w_y&\leq W(C_x)+W(C_y/C_x)\\ w_y&\leq W(C_y) \end{aligned} wywyW(Cx)+W(Cy/Cx)W(Cy)

于是结论得证。

那么接下来就是代码了,代码倒是十分清新:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 610

int n,m,f[maxn][maxn];
int A[maxn],w[maxn],S,T;
bool del[maxn],vis[maxn];
int solve(int x){
    
    
	memset(vis,false,sizeof(vis));
	memset(w,0,sizeof(w));w[0]=-1;
	for(int i=1;i<=n-x+1;i++){
    
    
		int pos=0;
		for(int j=1;j<=n;j++)
			if(!del[j]&&!vis[j]&&w[j]>w[pos])pos=j;
		vis[pos]=true;A[i]=pos;
		for(int j=1;j<=n;j++)
			if(!del[j]&&!vis[j])w[j]+=f[pos][j];
	}
	S=A[n-x];T=A[n-x+1];
	return w[T];
}
int Stoer_Wagner(){
    
    
	int re=1e9;
	for(int i=1;i<n;i++){
    
    
		re=min(re,solve(i));
		del[T]=true;
		for(int j=1;j<=n;j++)
			f[S][j]+=f[T][j],f[j][S]+=f[j][T];
	}
	return re;
}

int main()
{
    
    
	scanf("%d %d",&n,&m);
	for(int i=1,x,y,z;i<=m;i++)
		scanf("%d %d %d",&x,&y,&z),f[x][y]+=z,f[y][x]+=z;
	printf("%d",Stoer_Wagner());
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/113979871