zoukankan      html  css  js  c++  java
  • 【Learning】最小点覆盖(二分图匹配) 与Konig定理证明

    (附一道例题) 

    Time Limit: 1000 ms   Memory Limit: 128 MB

    Description

      最小点覆盖是指在二分图中,用最小的点集覆盖所有的边。当然,一个二分图的最小点覆盖可能有很多种。

      现在给定一个二分图,请你把图中的点分成三个集合:

      如果在任何一种最小点覆盖中都不包含这个点,则认为该点属于N集合。

      如果在任何一种最小点覆盖中都包含这个点,则认为该点属于A集合。

      如果一个点既不属于N集合,又不属于A集合,则认为该点属于E集合。

    Input

      第一行包含三个整数n, m, k,分别表示二分图A侧点的数量,二分图B侧点的数量,边的数量。

      接下来k行,每行两个整数i, j,分别表示二分图A侧第i号点与二分图B侧第j号点有连边。

      数据保证无重边。

    Output

      第一行输出一个长度为n的字符串,其中第i个字符表示二分图A侧第i个点所属的集合。

      第二行输出一个长度为m的字符串,其中第i个字符表示二分图B侧第i个点所属的集合。

    Sample Input Sample Output

    11 9 22
    1 1
    1 2
    1 3
    1 8
    1 9
    2 1
    2 3
    3 2
    3 4
    4 3
    4 5
    5 2
    5 4
    5 6
    6 6
    6 7
    7 5
    7 7
    8 7
    9 7
    10 7
    11 7

    AEEEEEENNNN
    EEEEEEANN


























    Hint

      对于10%的数据,$1 leq n, m leq 5$

      对于40%的数据,$1 leq n, m leq 100$

      对于100%的数据,$1 leq n, m leq 1000, 0 leq k leq n*m$

      


    Konig定理

      ORZ贴上Matrix67的博http://www.matrix67.com/blog/archives/116ORZ,这里证明了Konig定理,那我按着他的思路再述说一次。(我从这里才看懂的)。

      以及一篇思路很清晰的博:http://www.renfei.org/blog/bipartite-matching.html,里面直观地讲述了Hungary的本质,但主要还是看交错轨和增广路的概念即可。

      Konig定理:最小点覆盖的点集合大小等于最大匹配数。

      其中提到了最小点覆盖是如何找出的:

        1.正常二分图匹配。

        2.从右部找到未被匹配的点,走交错轨(先未匹配边后有匹配边,反复交错),将交错轨经过的点打上标记;

        3.左部有标记的点和右部无标记的点构成了最小点覆盖集。

      弄明白原理,这道题就很容易了。

      ORZMatrix67,我们分三步证明:

      

      1.这样弄出的点总共只有m个:

        我们的出发点总是右部未匹配的点。由于现在的图是最大匹配,交错轨一定是从右部出发,在右部结束,也就是结束时的边一定是匹配边(否则就满足增广路的性质,这张图就不是最大匹配了,矛盾);从右往左走的一定是非匹配边,从左往右走的一定是匹配边

        贴张图(出于Matrix67的博)

        

        发现:每条匹配边的某一端都是一个最小点覆盖集中的点。Why?

        我们选择的点中:左半部分有标记的,那都是在走交错轨的时候通过匹配边从右往左走过来的,于是交错轨中经过的左部点与匹配边一一对应。

        右半部分无标记的,代表没有交错轨经过,那是因为我们开始走的时候选的是未匹配点作为起点,且途中从左往右走的是匹配边,二者都没有这一个点的份。因此?因此它们是匹配点,也就是与匹配边一一对应。

        得证这样选出的点总共只有m个。Get√

      2.这样弄出的m个点能覆盖全部边:

         先选出的交错轨已经覆盖了一定数量的边;那么为什么选择右边无标记的点就可以覆盖剩下的边?

         首先,交错轨中的边一定是两端都有标记的;不是两端都有标记的边不属于交错轨。

         其次,左边无标记而右边有标记的边是不存在的:如果这是一条匹配边,那么右边的标记只能是从左边走来的,那么左边也应有标记;如果这是一条非匹配边,那么一定能从右边走到左边,左边也应该有标记。

          那么,只剩下左无右无、左有右无两种边存在,发现右端都是“无”,那么我们选择右端未被标记的点,就可以覆盖剩下的全部边。

      

      3.这样弄出的最小覆盖集是最少的(这不废话吗):

         覆盖所有的匹配边就至少需要m个点,嗯还能再少吗......

      证毕。


    题解

      根据上面提到的选点的方式,选的是左边有标记的点与右边无标记的点。

      出发时一定要选右部未匹配的点,也就是它们一定会打上标记。然而我们最终选的是右边无标记的点,所以右部未匹配的点一定不在最小覆盖集内---->N。

      左部未匹配的点,意味着交错轨从未经过,也就是它们一定没有标记。我们选择的是左边有标记的点,所以左部未匹配的点一定不在最小覆盖集内---->N。

      所以未匹配的点(暂且归为集合A)一定不在最小覆盖集中。

      既然A不在,那么与它们相连的点(归为集合B)就必须在--->A,不然就无法覆盖到它们之间相连的边了。

      那么B中的点,如果是匹配点,那么它的另一侧匹配点就不能在。当该点为左部点时,右边应该打上标记,不会被选;当该点为右部点的时候,它应该没有标记,属于选择的点的第二类点(右部无标记点),那么左边也就是另一侧匹配点就没必要选。

      

      重复上面判断步骤,判断出A和N的存在,剩下的就是E。这一步可以用类似广搜的操作完成。


     1 #include <cstdio>
     2 #include <cstring>
     3 #include <queue>
     4 #define mp make_pair
     5 using namespace std;
     6 typedef pair<int,int> pii;
     7 const int N=1010;
     8 int n,m,k,use[N*2],from[N*2],h[N*2],tot,col[N*2];
     9 struct Edge{int v,next;}g[N*N*2];
    10 queue<pii> q;
    11 inline void addEdge(int u,int v){
    12     g[++tot].v=v; g[tot].next=h[u]; h[u]=tot;
    13 }
    14 bool Hungary(int u){
    15     for(int i=h[u],v;i;i=g[i].next){
    16         if(!use[v=g[i].v]){
    17             use[v]=1;
    18             if(!from[v]||Hungary(from[v])){
    19                 from[v]=u;
    20                 return true;
    21             }
    22         }
    23     }
    24     return false;
    25 }
    26 int main(){
    27     scanf("%d%d%d",&n,&m,&k);
    28     for(int i=1,u,v;i<=k;i++){
    29         scanf("%d%d",&u,&v);
    30         addEdge(u,v+n); addEdge(v+n,u);
    31     }
    32     for(int i=1;i<=n;i++){
    33         memset(use,0,sizeof use);
    34         Hungary(i);
    35     }
    36     for(int i=1;i<=m;i++)
    37         if(from[n+i])
    38             from[from[n+i]]=n+i;
    39     for(int i=1;i<=n+m;i++)
    40         if(!from[i]&&!col[i]){
    41             col[i]=1;
    42             q.push(mp(i,2));
    43         }
    44     int u,f;
    45     pii s;
    46     while(!q.empty()){
    47         s=q.front(); q.pop();    
    48         u=s.first; f=s.second;
    49         for(int i=h[u],v;i;i=g[i].next)
    50             if(!col[v=g[i].v]&&(col[u]==1)||(col[u]==2&&v==from[u])){
    51                 col[v]=f;
    52                 q.push(mp(v,f==1?2:1));
    53             }
    54     }
    55     for(int i=1;i<=n;i++)
    56         if(col[i]==1) putchar('N');
    57         else if(col[i]==2) putchar('A');
    58         else putchar('E');
    59     puts("");
    60     for(int i=n+1;i<=n+m;i++)
    61         if(col[i]==1) putchar('N');
    62         else if(col[i]==2) putchar('A');
    63         else putchar('E');
    64     puts("");
    65     return 0;
    66 }
    奇妙代码
  • 相关阅读:
    arcgis server 9.2代码阅读笔记一:在图层中增加一个点
    mapx+vb实战摘要
    F# 程式設計入門 (1)
    arcgis server 9.2代码阅读笔记二:在页面上动态加载图层
    F#维基百科,自由的百科全书(重定向自F#)
    用VC++进行MapX二次开发
    sdsalea process in sd
    abap关于sap地址,传真,邮箱的地址读取
    SDEnterprise Structure Configuration
    ABAP通过LDB_PROCESS函数使用逻辑数据库
  • 原文地址:https://www.cnblogs.com/RogerDTZ/p/7625176.html
Copyright © 2011-2022 走看看