zoukankan      html  css  js  c++  java
  • POJ 2914 Minimum Cut (最小割)

    Minimum Cut
    Time Limit: 10000MS   Memory Limit: 65536K
    Total Submissions: 6403   Accepted: 2663
    Case Time Limit: 5000MS

    Description

    Given an undirected graph, in which two vertices can be connected by multiple edges, what is the size of the minimum cut of the graph? i.e. how many edges must be removed at least to disconnect the graph into two subgraphs?

    Input

    Input contains multiple test cases. Each test case starts with two integers N and M (2 ≤ N ≤ 500, 0 ≤ M ≤ N × (N − 1) ⁄ 2) in one line, where N is the number of vertices. Following are M lines, each line contains M integersAB and C (0 ≤ AB < NA ≠ BC > 0), meaning that there C edges connecting vertices A and B.

    Output

    There is only one line for each test case, which contains the size of the minimum cut of the graph. If the graph is disconnected, print 0.

    Sample Input

    3 3
    0 1 1
    1 2 1
    2 0 1
    4 3
    0 1 1
    1 2 1
    2 3 1
    8 14
    0 1 1
    0 2 1
    0 3 1
    1 2 1
    1 3 1
    2 3 1
    4 5 1
    4 6 1
    4 7 1
    5 6 1
    5 7 1
    6 7 1
    4 0 1
    7 3 1

    Sample Output

    2
    1
    2

    Source

    Baidu Star 2006 Semifinal 
    Wang, Ying (Originator) 
    Chen, Shixi (Test cases)
     
     

    一个无向连通网络,去掉一个边集可以使其变成两个连通分量则这个边集就是割集;最小割集当然就权和最小的割集。

    可以用最小切割最大流定理:

    1.min=MAXINT,确定一个源点

    2.枚举汇点

    3.计算最大流,并确定当前源汇的最小割集,若比min小更新min

    4.转到2直到枚举完毕

    5.min即为所求输出min

        不难看出复杂度很高:枚举汇点要O(n),最短增广路最大流算法求最大流是O((n^2)m)复杂度,在复杂网络中O(m)=O(n^2),算法总复杂度就是O(n^5);哪怕采用最高标号预进流算法求最大流O((n^2)(m^0.5)),算法总复杂度也要O(n^4)

        所以用网络流算法求解最小割集复杂度不会低于O(n^4)。

    ---------

        prim算法不仅仅可以求最小生成树,也可以求“最大生成树”。最小割集Stoer-Wagner算法就是典型的应用实例。

        求解最小割集普遍采用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)

    这个Stoer-Wagner算法可以参见这篇paper(http://docs.google.com/fileview?id=0BwxLvD9mcDNtMjk3MWVkMTAtZjMzNi00ZWE3LTkxYjQtYTQwNzcyZTk3Njk2&hl=en), 其核心思想是迭代缩小规模, 算法基于这样一个事实:

     

    对于图中任意两点s和t, 它们要么属于最小割的两个不同集中, 要么属于同一个集.

     

    如果是后者, 那么合并s和t后并不影响最小割. 基于这么个思想, 如果每次能求出图中某两点之间的最小割, 然后更新答案后合并它们再继续求最小割, 就得到最终答案了. 算法步骤如下:

     

    1. 设最小割cut=INF, 任选一个点s到集合A中, 定义W(A, p)为A中的所有点到A外一点p的权总和.

    2. 对刚才选定的s, 更新W(A,p)(该值递增).

    3. 选出A外一点p, 且W(A,p)最大的作为新的s, 若A!=G(V), 则继续2.

    4. 把最后进入A的两点记为s和t, 用W(A,t)更新cut.

    5. 新建顶点u, 边权w(u, v)=w(s, v)+w(t, v), 删除顶点s和t, 以及与它们相连的边.

    6. 若|V|!=1则继续1.

     

    看起来很简单, 每次像做最大生成树一样选最大"边"(注意, 这里其实不是边, 而是已经累计的权值之和, 就当是加权的度好了), 然后把最后进入的两个点缩到一块就可以了. 合并点最多有n-1次, 而不加堆优化的prim是O(n^2)的, 所以最终复杂度O(n^3), 要是你有心情敲一大坨代码, 还可以在稀疏图上用Fibonacci Heap优化一下, 不过网上转了一圈, 大多都是说能用Fibonacci Heap优化到怎样怎样的复杂度, 真正能自己写出来的恐怕也没几个, 看看uoregon(俄勒冈大学)的一大坨代码就有点寒. (http://resnet.uoregon.edu/~gurney_j/jmpc/fib.html)

     

    特别注意几个地方, 网上的好几个Stoer-Wagner版本都存在一些小错误:

     

    1. 算法在做"最大生成树"时更新的不是普通意义上的最大边, 而是与之相连的边的权值和, 当所有边都是单位权值时就是累计度.

    2. "最后进入A的两点记为s和t", 网上对s有两种解释, 一是在t之前一个加进去的点, 二是t的前趋节点, 也就是最后选择的那条边的另一端. 正解是第一种!

    3. 对于稠密图, 比如这题, 我用堆, 映射二分堆, 或者STL的优先队列都会TLE, 还不如老老实实O(n^3).

     

     

    另一篇论文:

     

    最小割 Stoer-Wagner 算法 
    Etrnls 2007-4-15 
    Stoer-Wagner 算法用来求无向图 G=(V, E)的全局最小割。 

    算法基于这样一个定理:对于任意s, t   V ∈ ,全局最小割或者等于原图的s-t 最小割,或者等于将原图进行 Contract(s, 
    t)操作所得的图的全局最小割。 

    算法框架: 
    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 为原图的全局最
    小割  

    Contract 操作定义: 
    若不存在边(p, q),则定义边(p, q)权值w(p, q) = 0 
    Contract(a, b): 删掉点 a, b 及边(a, b),加入新节点 c,对于任意 v  V ∈ ,w(v, c) = w(c, v) = w(a, v) + w(b, 
    v) 

    求 G=(V, E)中任意 s-t 最小割的算法: 
    定义w(A, x) = ∑w(v[i], x),v[i]  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)

     
     
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    
    using namespace std;
    
    const int VM=520;
    const int INF=0x3f3f3f3f;
    
    int n,m,mincut,src,des;
    int map[VM][VM],dis[VM],vis[VM],combine[VM];
    
    void Search(){   //最大生成树
        int i,j,k;
        memset(vis,0,sizeof(vis));
        memset(dis,0,sizeof(dis));
        src=des=-1;
        int tmp;
        for(i=0;i<n;i++){
            tmp=-INF;
            for(j=0;j<n;j++)
                if(!combine[j] && !vis[j] && tmp<dis[j]){
                    k=j;
                    tmp=dis[j];
                }
            if(k==des)
                return ;    
            src=des;  des=k;    //最后两个扩展的顶点。
            mincut=dis[k];
            vis[k]=1;
            for(j=0;j<n;j++)
                if(!combine[j] && !vis[j])
                    dis[j]+=map[k][j];
        }
    }
    
    int Stoer_Wagner(){
        memset(combine,0,sizeof(combine));
        int ans=INF;  //min=MAXINT,固定一个顶点P
        for(int i=0;i<n-1;i++){
            Search();   //从点P用“类似”prim的s算法扩展出“最大生成树”,记录最后扩展的顶点和最后扩展的边
            ans=min(ans,mincut);    //计算最后扩展到的顶点的切割值(即与此顶点相连的所有边权和),若比min小更新min
            if(ans==0)  //图不连通时最小割为0
                return 0;
            combine[des]=1;
            for(int j=0;j<n;j++)    //合并最后扩展的那条边的两个端点为一个顶点
                if(!combine[j]){
                    map[src][j]+=map[des][j];
                    map[j][src]+=map[j][des];
                }
        }
        return ans;
    }
    
    int main(){
    
        freopen("input.txt","r",stdin);
    
        while(~scanf("%d%d",&n,&m)){
            memset(map,0,sizeof(map));
            int u,v,w;
            while(m--){
                scanf("%d%d%d",&u,&v,&w);
                map[u][v]+=w;
                map[v][u]+=w;
            }
            printf("%d\n",Stoer_Wagner());
        }
        return 0;
    }
  • 相关阅读:
    【原理】【重点】异步回调的一些实现策略
    上下文传递
    洋码头全异步服务框架
    秒杀系统架构优化思路
    从urllib2的内存泄露看python的GC python引用计数 对象的引用数 循环引用
    jvisualvm All-in-One Java Troubleshooting Tool
    小心踩雷,一次Java内存泄漏排查实战
    django 请求处理流程 链路追踪
    存储过程
    Dijkstra's algorithm
  • 原文地址:https://www.cnblogs.com/jackge/p/3061669.html
Copyright © 2011-2022 走看看