【Stoer_Wagner】算法学习

此算法建议通过题目Minimum Cut POJ2914 来理解。

题目链接:Minimum Cut POJ2914

Stoer_Wagner算法用于求解全图最小割。复杂度为O( n 3 n^{3} n3),其思路为:

1、 固定一个点P,定义mincut为inf。
2、 从点P出发,类似Prim扩展出“最大生成树”(事实上不是最大生成树,我们把这个树命名为“XJB树”,这里的最大“边”是一种累加的权值)。
注:

3、 记录最后一次扩展的两点,将扩展的最后一点的割“边”与mincut比较取最小。
4、 合并最后扩展的两点(最后一次扩展的点为t,倒数第二次扩展的点为s,将t合并到s中)。
5、 回到第2步,合并n-1次,即只剩下两点后结束。
6、 此时,mincut即为答案。

图例:


注:在合并点的时候,边的权值也要更新。

第一步
在这里插入图片描述
第二步
在这里插入图片描述
第三步
在这里插入图片描述
第四、五步
在这里插入图片描述

第六、七步在这里插入图片描述

确定s和t的方法(以第一步为例):
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

常见的Stoer_Wagner有两种(以POJ2914为例):

第一种(3438ms):

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int N=5e2+5;
const int inf=0x3f3f3f3f;

int n,m;
int g[N][N];
int belong[N],dis[N];
bool vis[N];
//vis记录扩展“XJB树”时的缩点
//belong记录每个点所属的块

int Stoer_Wagner()
{
    int ans=inf;
    for(int i=0;i<n;i++) belong[i]=i;    //开始时每个点都是独立的
    while(n>1)  //此处n表示要进行n-1次缩点
    {
        int pre=0;  //记录前一个加入XJB树的点
        memset(vis,false,sizeof(vis));
        memset(dis,0,sizeof(dis));
        for(int i=1;i<n;i++)   //此处n表示要进行n-1次缩点 
        {
            int tem=-1;
            for(int j=1;j<n;j++) //代表从1到n-1的点(默认XJB树从0开始扩展)
                if(!vis[belong[j]])
                {
                    dis[belong[j]]+=g[belong[pre]][belong[j]];     //更新树外点到XJB树的距离
                    if(tem==-1||dis[belong[tem]]<dis[belong[j]])  //寻找距离当前XJB树最远的点
                        tem=j;
                }
            vis[belong[tem]]=true;

            if(i==n-1)  //全部点都进入了XJB树
            {
                int st=belong[pre], en=belong[tem];   //记录st和en
                ans=min(ans,dis[en]);   //更新最小割


                for(int j=0;j<n;j++)
                {
                    g[st][belong[j]]+=g[belong[j]][en];
                    g[belong[j]][st]+=g[belong[j]][en];
                }
                belong[tem]=belong[--n];  //将en点合并,并总体点数-1
            }
            pre=tem;    //更新前一个加入XJB树的点
        }
    }
    return ans;
}

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        memset(g,0,sizeof(g));
        for(int i=0;i<m;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            g[u][v]+=w; g[v][u]+=w;
        }
        printf("%d\n",Stoer_Wagner());
    }

    return 0;
}

第二种(8704ms):

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int N=5e2+5;
const int inf=0x3f3f3f3f;

int n,m;
int g[N][N],dis[N];
bool vis[N],merge[N];   
//vis记录扩展“XJB树”时的缩点
//merge记录每一次扩展“XJB树”后的缩点

int contract(int &st,int &en)   //寻找st和en
{
    int res;    //事实上,res的值就是最后一个点与XJB树之间的“距离”
    memset(dis,0,sizeof(dis));
    memset(vis,false,sizeof(vis));
    for(int i=1;i<=n;i++)   //此处n代表生成XJB树要进行n次缩点
    {
        int tem=-1, maxc=-1;
        for(int j=0;j<n;j++)    //此处n代表点为从0到n
            if(!merge[j]&&!vis[j]&&dis[j]>maxc)
                tem=j, maxc=dis[j]; //根据XJB树与树外点之间“距离”得到下一次更新到树上的点
        if(tem==-1) return res;
        st=en; en=tem; res=maxc; vis[tem]=true; //更新st、en、res的值以及tem点的状态
        for(int j=0;j<n;j++)
            if(!merge[j]&&!vis[j])
                dis[j]+=g[tem][j];  //更新各点到XJB树的距离
    }
    return res;
}

int Stoer_Wagner()
{
    int ans=inf,st,en;
    memset(merge,false,sizeof(merge));
    for(int i=1;i<n;i++)    //此处n代表要进行n-1次缩点
    {
        int tem=contract(st,en);    //得到该步的最小割
        merge[en]=true; ans=min(ans,tem);   //将en点合并,并更新总体最小割
        if(ans==0) return 0;
        //en合并到st上后,将st与其余个点之间的权值进行更新
        for(int j=0;j<n;j++)    //此处n代表点为从0到n
            if(!merge[j])
            {
                g[st][j]+=g[j][en];
                g[j][st]+=g[j][en];
            }
    }
    return ans;
}

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        memset(g,0,sizeof(g));
        for(int i=0;i<m;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            g[u][v]+=w; g[v][u]+=w;
        }
        printf("%d\n",Stoer_Wagner());
    }

    return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值