zoukankan      html  css  js  c++  java
  • 无向图匹配的带花树算法

    2012-01-30 18:06:16|  分类: 程序|字号 订阅

     
     
    在北京冬令营的时候,yby提到了“带花树开花”算法来解非二分图的最大匹配。
    于是,我打算看看这是个什么玩意。其实之前,我已经对这个算法了解了个大概,但是。。。真的不敢去写。
    有一个叫Galil Zvi的人(应该叫计算机科学家),写了篇论文:
    Efficient Algorithms for Finding Maximal Matching in Graphs
    (如果你在网上搜不到,可以:http://builtinclz.abcz8.com/art/2012/Galil%20Zvi.pdf
    这篇论文真神啊,它解决了4个问题:
    (一般图+二分图)的(最大匹配+最大权匹配)问题。
    算法的思想、故事,请自己看论文吧。
    这个论文告诉了我们很多有趣的东西,例如:
     用Dinic实现的二分图匹配的时间复杂度其实是O(M*N^0.5),这也许能够解释为什么一般网络流算法比Hungry要快了。
    另外,带花树算法的正确性的证明比较困难;而其时间复杂度是可以做到O(M*N^0.5)的,不过要详细实现,那么就快能到“ACM最长论文奖”了。
     
    我写了一个实例代码:

    http://builtinclz.abcz8.com/art/2012/ural1099.cpp

    没错,这是用来解决URAL 1099 Work Schedule那题的。时间复杂度是O(N^3)

    简述一下“带花树”算法吧:
    它的核心思想还是找增广路。假设已经匹配好了一堆点,我们从一个没有匹配的节点s开始,使用BFS生成搜索树。每当发现一个节点u,如果u还没有被匹配,那么就可以进行一次成功的增广;否则,我们就把节点u和它的配偶v一同接到树上,之后把v丢进队列继续搜索。我们给每个在搜索树上的点一个类型:S或者T。当u把它的配偶v扔进队列的时候,我们把u标记为T型,v标记为S型。于是,搜索树的样子是这样的:
           s
          /  
             
         |    |
         c    d
            
           u j
        | |  | |
        i j  v k
    其中,黑色竖线相连的两个点是已经匹配好的,蓝色斜线表示两个点之间有边,但是没有配对。T型的用红色,S型的用黑色。
     
    这里有个小问题:一个S型点d在某一步扩展的时候发现了点u,如果u已经在搜索树上了(即,出现了环),怎么办?
    我们规定,如果u的类型是T型,就无视这次发现;(这意味着我们找到了一个长度为偶数的环,直接无视)
           s
          /  
             
         |    |
         c    d   如果连出来的边是指向T型点的,就无视这个边。
            
           
        | |    |
        i j    k
    否则,我们找到了一个长度为奇数的环,就要进行一次“缩花”的操作!所谓缩花操作,就是把这个环缩成一个点。
           s
          /  
             
         |    |
         c    d
            
            
        | |   |
        i u<-+ k
    这个图缩花之后变成了5个点(一个大点,或者叫一朵花,加原来的4个点):
    缩点完成之后,还要把原来环里面的T型点统统变成S型点,之后扔到队列里去。
      +-------------+
      |             |
      |     s       |
      |    /  \     
      |           
      |   |    |    |   现在是一个点了!还是一个S点。
      |   c    d    |
      |     / \   
    --    ------
     |             |   
     |             |   
     |             |   
     +-------------+   
    |                   
    e                   
    |                   |
    i                   k
    为什么能缩成一个点呢?我们看一个长度为奇数的环(例如上图中的s-b-d-j-f-c-a-),如果我们能够给它中的任意一个点找一个出度(配偶),那么环中的其他点正好可以配成对,这说明,每个点的出度都是等效的。例如,假设我们能够图中的点d另找一个配偶(例如d'好了),那么,环中的剩下6个点正好能配成3对,一个不多,一个不少(算上d和d'一共4对刚刚好)。
    b-dd'         a s-b d-d'
     \           =>    \     
      cf-u              c f-u
    这就是我们缩点的思想来源。有一个劳苦功高的计算机科学家证明了:缩点之前和缩点之后的图是否有增广路的情况是相同的。
    缩起来的点又叫一朵花(blossom).
    注意到,组成一朵花的里面可能嵌套着更小的花。
     
    当我们最终找到一条增广路的时候,要把嵌套着的花层层展开,还原出原图上的增广路出来。
     
    嗯,现在你对实现这个算法有任何想法吗?
    天啊,还要缩点……写死谁。。。。。。
    我一开始也是这么想的。
    我看了一眼网上某个大牛的程序,之后结合自己的想法,很努力地写出了一个能AC的版本。
    实现的要点有什么呢?
    首先,我们不“显式”地表示花。我们记录一个Next数组,表示最终增广的时候的路径上的后继。同时,我们维护一个并查集,表示每个点现在在以哪个点为根的花里(一个花被缩进另一朵花之后就不算花了)。还要记录每个点的标记。
    主程序是一段BFS。对于每个由x发展出来的点y,分4种情况讨论:
    1。xy是配偶(不说夫妻,这是非二分图。。。)或者xy现在是一个点(在一朵花里):直接无视
    2。y是T型点:直接无视
    3。y目前单身:太好了,进行增广!
    4。y是一个S型点:缩点!缩点!
    缩点的时候要进行的工作:
    1。找x和y的LCA(的根)p。找LCA可以用各种方法。。。直接朴素也行。
    2。在Next数组中把x和y接起来(表示它们形成环了!)
    3。从x、y分别走到p,修改并查集使得它们都变成一家人,同时沿路把Next数组接起来。
     
    Next数组很奇妙。每时每刻,它实际形成了若干个挂在一起的双向链表来表示一朵花内部的走法。
         ----
        /    \--+
        |    |   |
        |    |--+
            
       ----------
      /          \
    +-            --+
    |               |
    |               |
    +----s  ------+     
     
     
     
    有权图的最大匹配怎么做?
    看论文吧。。。用类似KM的方法,不过,是给每个花再来一个权值。真的很复杂。。。
    有一个人写了代码,好像是GPL许可证。。。你最好想办法搜到它的网站来看看版权的问题;总之,我先贴出来:

     转载地址:http://fanhq666.blog.163.com/blog/static/8194342620120304463580/

  • 相关阅读:
    Java.io.outputstream.PrintStream:打印流
    Codeforces 732F. Tourist Reform (Tarjan缩点)
    退役了
    POJ 3281 Dining (最大流)
    Light oj 1233
    Light oj 1125
    HDU 5521 Meeting (最短路)
    Light oj 1095
    Light oj 1044
    HDU 3549 Flow Problem (dinic模版 && isap模版)
  • 原文地址:https://www.cnblogs.com/LUO257316/p/3277871.html
Copyright © 2011-2022 走看看