来自文章:A Simple Min-Cut Algorithm。作者:MECHTHILD STOER,FRANK WAGNER。(看了一遍原文,想记录下来,看到http://www.cnblogs.com/ylfdrib/archive/2010/08/17/1801784.html 上有简介就摘过来了)
算法基于这样一个定理:对于任意s, t ∈ V ,全局最小割或者等于原图的s-t 最小割,或者等于将原图进行 Contract(s, t)操作所得的图的全局最小割(显然的,将s、t文类讨论,属于最小割的两部还是其中一部)。
假设不存在边(p, q),则定义边(p, q)权值w(p, q) = 0 。
Contract 操作定义: Contract(a, b): 删掉点 a, b 及边(a, b),加入新节点 c,对于任意 v∈V ,w(v, c) = w(c, v) = w(a, v) + w(b, v)
算法框架:
1. 设当前找到的最小割MinCut 为+∞
2. 在 G中求出任意 s-t 最小割 c(方法见下文),MinCut = min(MinCut, c)
3. 对 G作 Contract(s, t)操作,得到 G'=(V', E'),若|V'| > 1,则G=G'并转 2,否则MinCut 为原图的全局最小割。
求 G=(V, E)中任意 s-t 最小割的算法:
定义w(A, x) = ∑w(v[i], x),v[i]∈A,x∉A。
定义 Ax 为在x 前加入 A 的所有点的集合(不包括 x)
1. 令集合 A={a},a为 V中任意点
2. 选取 V - A中的 w(A, x)最大的点 x加入集合 A
3. 若|A|=|V|,结束
令倒数第二个加入 A的点为 s,最后一个加入 A的点为 t,则s-t 最小割为 w(At, t)(即t为一部,剩下的为另一部)。用归纳法证明,不难看懂。
2914题意:裸的全局最小割,两点之间可能有多条边,那么边数就当做权值即可。
思路:上述算法(未加堆优化,时间复杂度O(n^3))。
#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
using namespace std;
#define INF 0x3fffffff
#define clr(s,t) memset(s,t,sizeof(s));
#define N 505
int g[N][N],n,m;
int del[N],used[N],dis[N];
int test(int num){
int i,j,s,t,now,mm;
clr(used, 0);
for(i = 1;i<n;i++)
if(!del[i])
dis[i] = g[0][i];
for(i = 1,t = 0;i<num;i++){
mm = 0;
for(j = 1;j<n;j++){//找新加入的点,即和已加入点集里面点的路径和最大的点
if(!del[j] && !used[j] && dis[j]>mm){
mm = dis[j];
now = j;
}
}
if(mm==0)//说明找不到,不连通,那么连通度必然为0
return 0;
used[now] = 1;
s = t;
t = now;
for(j = 1;j<n;j++)//更新距离
if(!del[j] && !used[j])
dis[j] += g[now][j];
}
del[t] = 1;//缩点,将t缩到s
for(i = 0;i<n;i++)
if(!del[i] && i!=s)
g[i][s] = (g[s][i] += g[i][t]);
return dis[t];
}
int main(){
while(scanf("%d %d",&n,&m)!=EOF){
int i,a,b,c,res=INF;
clr(g, 0);
clr(del, 0);
for(i = 1;i<=m;i++){
scanf("%d %d %d",&a,&b,&c);
g[a][b] += c;
g[b][a] += c;
}
for(i = 0;i<n-1;i++){
res = min(res,test(n-i));
if(!res)
break;
}
printf("%d\n",res);
}
return 0;
}