/*
求解最小割集普遍采用Stoer-Wagner算法:
1.min=MAXINT,固定一个顶点P
2.从点P用类似prim的s算法扩展出“最大生成树”,记录最后扩展的顶点和最后扩展的边
每次像做最大生成树一样选最大"边"(注意, 这里其实不是边, 而是已经累计的权值之和, 就当是加权的度好了)
3.计算最后扩展到的顶点的切割值(即与此顶点相连的所有边权和),若比min小更新min
4.合并最后扩展的那条边的两个端点为一个顶点(当然他们的边也要合并)
5.转到2,合并N-1次后结束
6.min即为所求,输出min
prim本身复杂度是O(n^2),合并n-1次,算法复杂度即为O(n^3)
如果在prim中加堆优化,复杂度会降为O((n^2)logn)
*/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=510;
const int INF=0x3ffffff;
int mat[maxn][maxn];
int dist[maxn];
int node[maxn];
bool used[maxn];
int mincut(int n)
{
int last,pre,ret=INF;
for(int i=0;i<n;i++)node[i]=i;//判断是否删除,实际上node[i]!=i意味已删除,
while(n>1)
{
last=1; //记录最远的点,pre->last最后扩展的边
memset(used,0,sizeof(used));
used[node[0]]=1; //设node[0]为p点
for(int i=1;i<n;i++)
{
dist[node[i]]=mat[node[0]][node[i]]; //固定定点P为node[0],这里初始化dist
if(dist[node[i]]>dist[node[last]])last=i;
}
pre=0;
for(int i=1;i<n;i++)
{
if(i==n-1) //生成树的最后一条边
{
ret=min(ret,dist[node[last]]);//更新最小割
for(int k=0;k<n;k++) //合并pre和last两点 ,更新中的mat.
mat[node[k]][node[pre]]=mat[node[pre]][node[k]]+=mat[node[k]][node[last]];
node[last]=node[--n]; //删掉last结点 ,
}
used[node[last]]=1;
pre=last;
last=-1;
for(int j=1;j<n;j++) //更新到树的和距离,即权值
if(!used[node[j]])
{
dist[node[j]]+=mat[node[j]][node[pre]];
if(last==-1||(dist[node[last]]<dist[node[j]]))//优化prim,合并求下一个last,所以之前已赋值last=-1
last=j;
}
}
}
return ret;
}
int main()
{
int n,m;
while (scanf("%d%d",&n,&m)!=-1)
{
memset(mat,0,sizeof(mat));
for (int i=1; i<=m; i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
mat[a][b]+=c;
mat[b][a]+=c;
}
printf("%d\n",mincut(n));
}
return 0;
}
引理1.任意在无向图G中选出s和t. 最小割的值要么等于以s和t为源汇点的最小割,要么等于将s和t缩点之后的全局最小割。
简单说明,如果存在一个最小割,s和t分别在两个点集中,那么s-t最小割就等于全局最小割,如果不是,那么s和t就在同一侧点集,缩点之后肯定不影响最小割的求解。将s和t缩点的操作就是将新建一点 ,该点到任意点的边权等于s到该点的边权与t到该点的边权之和。
既然有了这个结论,那大体的算法框架就是,找任意s和t的最小割,然后缩点,总点数减一,重复n-1次之后,那么就剩下一个点,算法结束。
现在的难点就是如何在最短的时间内找到任意s和t之间的最小割大小。
引理2.初始集合A为空集,任意选择无向图中一点进入集合,开数组记录到达每一个点的边权和,边权和更新加上每次进入集合的点到达其他不在集合内的点的边权,然后每次从不在集合内的所有点中选择一点累计边权和最大的点进入集合,直至所有点进入A中,最后一个进入A的点为t,t之前一个进入集合的点为s,则t进入集合时的边权和就是s和t之间的最小割。(我也不懂)