题意
给出一个有边权的无向图,求最小割。
思路
如果按照网络流的做法,通过求最大流来求最小割。就需要确定一个源点然后枚举汇点,这样的复杂度极高,据说有O(n5)。所以这个题并不是这么做,而是用stoer_wagner算法。这一算法在求最小割的问题中具有普适性。
stoer_wagner算法
算法思路
整体思路和求最小生成树的prim算法有些相似,不过这里求的“有点像”“最大生成树”。构造一个集合A,一开始将任意一个点加入。之后重复地从剩下的点中选择点x,使集合A到x的流最大(也就是A内点到x的流之和),把x加入A中。把x加入A的时候要记得更新A到剩下点的流。一直重复到所有点都加入A中,则最后一个点的所有边的权值之和,就是以它为汇点,最后一个加入A的点为源点的最小割。此时更新最小割的值,然后将这两个点合并成一个点,重复上述操作,直到所有的点都合并成了一个点。此时便获得了全局最小割的值。
合并操作合法嘛?
合法。最后的两个点有两种情况,一种是它们在全局最小割的同一端,将它俩合并并不影响最小割的值;一种是它俩在最小割的两端,如果是这样那么这一次更新全局最小割就已经得到了最终答案。
为什么最后一个点的所有边权值之和就是到前一个点的最小割?
如果不切断这其中的任何一条边,最后一个点都能和前一个点连起来。而且每次都选流最大的,最后一个点就是整个图中到其他点的流之和最小的点。
其他点可以嘛?
其他点不一定满足。如下图 第一次更新中,最后一个点是6,前一个点是5,此时6的边权之和是5到6的最小割,这没问题。但是换成0到6,就显然不对了。至于这原理,现在还没想明白。
它还满足最小割是最大流的规律嘛?
满足。从最后一个点出发的流必定都能流进前一个点。因为前一个点在A中的接口数不小于最后一个点。
算法流程
设最小割cut=INF, 任选一个点s到集合A中, 定义W(A, p)为A中的所有点到A外一点p的权总和.对刚才选定的s, 更新W(A,p)(该值递增).选出A外一点p, 且W(A,p)最大的作为新的s, 若A!=G(V), 则继续2.把最后进入A的两点记为s和t, 用W(A,t)更新cut.合并st,即新建顶点u, 边权w(u, v)=w(s, v)+w(t, v), 删除顶点s和t, 以及与它们相连的边.若|V|!=1则继续1.
题目链接
http://poj.org/problem?id=2914
AC代码
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn =
500 +
10;
const int inf =
0x3f3f3f3f;
int n, m;
int G[maxn][maxn];
int w[maxn];
bool vis[maxn];
bool com[maxn];
int stoer_wagner()
{
memset(com,
false,
sizeof com);
int min_cut = inf;
for(
int k=
0; k< n-
1; k++)
{
memset(w,
0,
sizeof w);
memset(vis,
false,
sizeof vis);
int s, t =
0;
for(
int i=
1; i< n-k; i++)
{
s = t, t = -
1;
for(
int j=
1; j< n; j++)
{
w[j] += G[s][j];
if(!vis[j] && !com[j]
&& (t == -
1 || w[t] < w[j]))
t = j;
}
vis[t] =
true;
}
if(min_cut > w[t]) min_cut = w[t];
for(
int i=
0; i< n; i++)
{
G[s][i] += G[t][i];
G[i][s] += G[i][t];
}
com[t] =
true;
}
return min_cut;
}
int main()
{
while(
scanf(
"%d %d", &n, &m) != EOF)
{
memset(G,
0,
sizeof G);
for(
int i=
0; i< m; i++)
{
int x, y, c;
scanf(
"%d %d %d", &x, &y, &c);
G[x][y] += c;
G[y][x] += c;
}
cout << stoer_wagner() << endl;
}
return 0;
}