zoukankan      html  css  js  c++  java
  • [ACM]二分图 最大匹配 最大权匹配 匈牙利 Hopcroft–Karp KuhnMunkres

    二分图求最大匹配的最常用算法是匈牙利算法,匈牙利算法的实质与求最大流的思想一致,通过寻求增光路来扩大匹配的个数,这里不详细介绍。

    König定理,求解二分图非常重要的一个定理,简洁的说就是:二分图中的最大匹配数等于这个图中的最小点覆盖数,因此求最大匹配和最小点覆盖是相辅相成的,证明这里不介绍。

    要详细看匈牙利算法的介绍到这:http://www.byvoid.com/blog/hungary/

    要详细理解König定理到这:http://www.matrix67.com/blog/archives/116

    求解匹配问题的主要障碍是构图,至于怎么构图,这个需要自己做题的积累,我也在摸索中……,以后若有心得会更新进来。

    只要将原问题抽象成二分图的匹配问题,且已构建好图,那么编写代码就没有难度了,因为匈牙利算法很好实现。

    求最大匹配的一个例题,POJ 3020 Antenna Placement

    题意求最小圈个数,使得所有的点被圈起来,圈的范围是上下或左右两个。此题若将每个点一拆为2,那么就可以抽象成一个最大匹配问题,点的个数-最大匹配就是答案,由于加入了与原先相同的一张图,两边都可以匹配对方,所以求得的匹配数是原图的2倍。

    View Code
     1 #include <iostream>
     2 #include <cstring>
     3 #include <cstdio>
     4 #include <cstdlib>
     5 #include <cmath>
     6 #include <string>
     7 #include <vector>
     8 #include <list>
     9 #include <map>
    10 #include <set>
    11 #include <queue>
    12 #include <stack>
    13 #include <bitset>
    14 #include <algorithm>
    15 #include <numeric>
    16 #include <functional>
    17 using namespace std;
    18 typedef long long ll;
    19 #define inf (int)1e10
    20 #define exp 1e-8
    21 #define read freopen("in.txt","r",stdin)
    22 #define write freopen("out.txt","w",stdout)
    23 #define maxn 405
    24 vector<int>vec[maxn];
    25 int mp[45][15];
    26 int head[maxn];
    27 bool vis[maxn];
    28 int uN;
    29 bool dfs(int u)
    30 {
    31     for(int i=0;i<vec[u].size();i++)
    32     {
    33         if(!vis[vec[u][i]])
    34         {
    35             vis[vec[u][i]]=true;
    36             if(head[vec[u][i]]==-1||dfs(head[vec[u][i]]))
    37             {
    38                 head[vec[u][i]]=u;
    39                 return true;
    40             }
    41         }
    42     }
    43     return false;
    44 }
    45 int hungary()
    46 {
    47     int ans=0;
    48     memset(head,-1,sizeof(head));
    49     for(int i=1;i<=uN;i++)
    50     {
    51         memset(vis,0,sizeof(vis));
    52         if(dfs(i))++ans;
    53     }
    54     return ans;
    55 }
    56 int main()
    57 {
    58     //read;
    59     int cas,row,col;
    60     char ss[55];
    61     scanf("%d",&cas);
    62     while(cas--)
    63     {
    64         for(int i=0;i<maxn;i++)vec[i].clear();
    65         uN=0;
    66         scanf("%d%d",&row,&col);
    67         memset(mp,0,sizeof(mp));
    68         for(int i=1;i<=row;i++)
    69         {
    70             scanf("%s",ss);
    71             for(int j=1;j<=col;j++)
    72                 if(ss[j-1]=='*')mp[i][j]=++uN;
    73         }
    74         for(int i=1;i<=row;i++)
    75             for(int j=1;j<=col;j++)
    76                 if(mp[i][j])
    77                 {
    78                     if(mp[i-1][j])vec[mp[i][j]].push_back(mp[i-1][j]);
    79                     if(mp[i][j-1])vec[mp[i][j]].push_back(mp[i][j-1]);
    80                     if(mp[i+1][j])vec[mp[i][j]].push_back(mp[i+1][j]);
    81                     if(mp[i][j+1])vec[mp[i][j]].push_back(mp[i][j+1]);
    82                 }
    83         printf("%d\n",uN-hungary()/2);
    84     }
    85     return 0;
    86 }

    上面说道,二分图中的最大匹配数等于这个图中的最小点覆盖数。一个例题hdu 1054 Strategic Game

    题意就是在一个树上求最少的点支配所有的边,即最小点覆盖,那么一个解法自然是求最大匹配,最后答案除2的原因同上。

    View Code
     1 #include <iostream>
     2 #include <cstring>
     3 #include <cstdio>
     4 #include <cstdlib>
     5 #include <cmath>
     6 #include <string>
     7 #include <vector>
     8 #include <list>
     9 #include <map>
    10 #include <set>
    11 #include <queue>
    12 #include <stack>
    13 #include <bitset>
    14 #include <algorithm>
    15 #include <numeric>
    16 #include <functional>
    17 using namespace std;
    18 typedef long long ll;
    19 #define inf (int)1e10
    20 #define exp 1e-8
    21 #define read freopen("in.txt","r",stdin)
    22 #define write freopen("out.txt","w",stdout)
    23 #define maxn 1505
    24 vector<int>vec[maxn];
    25 int head[maxn];
    26 bool vis[maxn];
    27 int uN;
    28 bool dfs(int u)
    29 {
    30     for(int i=0;i<vec[u].size();i++)
    31     {
    32         if(!vis[vec[u][i]])
    33         {
    34             vis[vec[u][i]]=true;
    35             if(head[vec[u][i]]==-1||dfs(head[vec[u][i]]))
    36             {
    37                 head[vec[u][i]]=u;
    38                 return true;
    39             }
    40         }
    41     }
    42     return false;
    43 }
    44 int hungary()
    45 {
    46     int ans=0;
    47     memset(head,-1,sizeof(head));
    48     for(int i=0;i<uN;i++)
    49     {
    50         memset(vis,0,sizeof(vis));
    51         if(dfs(i))++ans;
    52     }
    53     return ans;
    54 }
    55 int main()
    56 {
    57     //read;
    58     int n;
    59     while(~scanf("%d",&n))
    60     {
    61         uN=n;
    62         for(int i=0;i<maxn;i++)vec[i].clear();
    63         int u,v,w;
    64         while(n--)
    65         {
    66             scanf("%d:(%d)",&u,&w);
    67             while(w--)
    68             {
    69                 scanf("%d",&v);
    70                 vec[u].push_back(v);
    71                 vec[v].push_back(u);
    72             }
    73         }
    74         printf("%d\n",hungary()/2);
    75     }
    76     return 0;
    77 }

    匈牙利算法适合稠密图的计算,时间复杂度0(E*V)。匈牙利求增广路的代价比较高,对于点多的稀疏图常常将图延伸很深却还是“无功而返”,所以对于稠密图匈牙利算法的效率有所下降。

    Hopcroft–Karp算法是匈牙利算法的改进,时间复杂度0(E*V^1/2)(怎么证的?),算法实质其实还是匈牙利算法求增广路,改进的地方是在深度搜索增广路前,先通过广度搜索,查找多条可以增广的路线,从而不再让dfs“一意孤行”。其中算法用到了分层标记防止多条增广路重叠,这里是精髓,须仔细理解。Hopcroft–Karp的详细解释这里也不赘述。

    要详细理解Hopcroft–Karp算法到这(此博文也是转载的,原文实在找不到,呵呵):http://blog.sina.com.cn/s/blog_45b48d320100vpgc.html

    一个试模板的题目:SPOJ 4206 Fast Maximum Matching

    View Code
     1 #include <iostream>
     2 #include <cstring>
     3 #include <cstdio>
     4 #include <cstdlib>
     5 #include <cmath>
     6 #include <string>
     7 #include <vector>
     8 #include <list>
     9 #include <map>
    10 #include <set>
    11 #include <queue>
    12 #include <stack>
    13 #include <bitset>
    14 #include <algorithm>
    15 #include <numeric>
    16 #include <functional>
    17 using namespace std;
    18 typedef long long ll;
    19 #define inf (int)1e10
    20 #define exp 1e-8
    21 #define read freopen("in.txt","r",stdin)
    22 #define write freopen("out.txt","w",stdout)
    23 #define maxn 50005
    24 vector<int>vec[maxn];
    25 int headU[maxn],headV[maxn];
    26 int du[maxn],dv[maxn];
    27 int uN,vN;
    28 bool bfs()
    29 {
    30     queue<int>q;
    31     int dis=inf;
    32     memset(du,0,sizeof(du));
    33     memset(dv,0,sizeof(dv));
    34     for(int i=1;i<=uN;i++)
    35         if(headU[i]==-1)q.push(i);
    36     while(!q.empty())
    37     {
    38         int u=q.front();q.pop();
    39         if(du[u]>dis)break;
    40         for(int i=0;i<vec[u].size();i++)
    41             if(!dv[vec[u][i]])
    42             {
    43                 dv[vec[u][i]]=du[u]+1;
    44                 if(headV[vec[u][i]]==-1)dis=dv[vec[u][i]];
    45                 else
    46                 {
    47                     du[headV[vec[u][i]]]=dv[vec[u][i]]+1;
    48                     q.push(headV[vec[u][i]]);
    49                 }
    50             }
    51     }
    52     return dis!=inf;
    53 }
    54 bool dfs(int u)
    55 {
    56     for(int i=0;i<vec[u].size();i++)
    57         if(dv[vec[u][i]]==du[u]+1)
    58         {
    59             dv[vec[u][i]]=0;
    60             if(headV[vec[u][i]]==-1||dfs(headV[vec[u][i]]))
    61             {
    62                 headU[u]=vec[u][i];
    63                 headV[vec[u][i]]=u;
    64                 return 1;
    65             }
    66         }
    67     return 0;
    68 }
    69 int Hopcroft()
    70 {
    71     memset(headU,-1,sizeof(headU));
    72     memset(headV,-1,sizeof(headV));
    73     int ans=0;
    74     while(bfs())
    75       for(int i=1;i<=uN;i++)
    76           if(headU[i]==-1&&dfs(i))ans++;
    77     return ans;
    78 }
    79 int main()
    80 {
    81     //read;
    82     int u,v,w;
    83     while(~scanf("%d%d%d",&u,&v,&w))
    84     {
    85         for(int i=0;i<maxn;i++)vec[i].clear();
    86         uN=u;
    87         int tu,tv;
    88         while(w--)
    89         {
    90             scanf("%d%d",&tu,&tv);
    91             vec[tu].push_back(tv);
    92         }
    93         printf("%d\n",Hopcroft());
    94     }
    95     return 0;
    96 }

    另一个不拐弯题,HDU 2389 Rain on your Parade

    PS:将欧几里德距离算成图上距离WA了N次。

    View Code
      1 #include <iostream>
      2 #include <cstring>
      3 #include <cstdio>
      4 #include <cstdlib>
      5 #include <cmath>
      6 #include <string>
      7 #include <vector>
      8 #include <list>
      9 #include <map>
     10 #include <set>
     11 #include <queue>
     12 #include <stack>
     13 #include <bitset>
     14 #include <algorithm>
     15 #include <numeric>
     16 #include <functional>
     17 using namespace std;
     18 typedef long long ll;
     19 #define inf (int)1e10
     20 #define exp 1e-8
     21 #define read freopen("in.txt","r",stdin)
     22 #define write freopen("out.txt","w",stdout)
     23 #define maxn 3005
     24 int g[3][maxn];
     25 vector<int>vec[maxn];
     26 int headU[maxn],headV[maxn];
     27 int du[maxn],dv[maxn];
     28 int uN,vN;
     29 bool bfs()
     30 {
     31     queue<int>q;
     32     int dis=inf;
     33     memset(du,0,sizeof(du));
     34     memset(dv,0,sizeof(dv));
     35     for(int i=1;i<=uN;i++)
     36         if(headU[i]==-1)q.push(i);
     37     while(!q.empty())
     38     {
     39         int u=q.front();q.pop();
     40         if(du[u]>dis)break;
     41         for(int i=0;i<vec[u].size();i++)
     42             if(!dv[vec[u][i]])
     43             {
     44                 dv[vec[u][i]]=du[u]+1;
     45                 if(headV[vec[u][i]]==-1)dis=dv[vec[u][i]];
     46                 else
     47                 {
     48                     du[headV[vec[u][i]]]=dv[vec[u][i]]+1;
     49                     q.push(headV[vec[u][i]]);
     50                 }
     51             }
     52     }
     53     return dis!=inf;
     54 }
     55 bool dfs(int u)
     56 {
     57     for(int i=0;i<vec[u].size();i++)
     58         if(dv[vec[u][i]]==du[u]+1)
     59         {
     60             dv[vec[u][i]]=0;
     61             if(headV[vec[u][i]]==-1||dfs(headV[vec[u][i]]))
     62             {
     63                 headU[u]=vec[u][i];
     64                 headV[vec[u][i]]=u;
     65                 return 1;
     66             }
     67         }
     68     return 0;
     69 }
     70 int Hopcroft()
     71 {
     72     memset(headU,-1,sizeof(headU));
     73     memset(headV,-1,sizeof(headV));
     74     int ans=0;
     75     while(bfs())
     76       for(int i=1;i<=uN;i++)
     77           if(headU[i]==-1&&dfs(i))ans++;
     78     return ans;
     79 }
     80 bool check(int x,int y,int dis)
     81 {
     82     return x*x+y*y<=dis*dis;
     83 }
     84 int main()
     85 {
     86     //read;
     87     int cas;
     88     scanf("%d",&cas);
     89     for(int x=1;x<=cas;x++)
     90     {
     91         int t,u,v;
     92         scanf("%d%d",&t,&u);
     93         uN=u;
     94         for(int i=0;i<maxn;i++)vec[i].clear();
     95         for(int i=1;i<=u;i++)
     96             scanf("%d%d%d",&g[0][i],&g[1][i],&g[2][i]);
     97         scanf("%d",&v);
     98         int tx,ty;
     99         for(int i=1;i<=v;i++)
    100         {
    101             scanf("%d%d",&tx,&ty);
    102             for(int j=1;j<=u;j++)
    103                 if(check(tx-g[0][j],ty-g[1][j],t*g[2][j]))
    104                     vec[j].push_back(i);
    105         }
    106         printf("Scenario #%d:\n%d\n\n",x,Hopcroft());
    107     }
    108     return 0;
    109 }

    为以后上需要分析构图的难题占个坑……。

    (我是坑)

    以上内容于2013/1/23 记

    在原来的基础上趁热打铁,学习了求二分图最大权匹配的KM算法。

    首先要明确一个概念,什么是最大权匹配?

    先说带权匹配,带权匹配自然是求一个匹配中边权的和,那么最大权匹配是不是求一个匹配,使得边权和最大呢?不是这样的,最大权匹配必须是在保证该匹配是完备匹配的基础上权值和最大。而完备匹配是指一个匹配它包含二分图两个点集中某一个的全集(当然也可以包括这两个全集,也就是完美匹配)。换句话说,不是完备的匹配根本没有资格论最大权。至于仅保证权值和最大的匹配是什么,这我也不清楚……。

    学习图论,概念一定要弄清,看到很多网上资料将完备匹配当作是完美匹配,这是错误的。

    KM算法的具体实现这里也不详细介绍。

    要详细看KM算法的介绍到这:http://www.nocow.cn/index.php/Kuhn-Munkres%E7%AE%97%E6%B3%95

    另外,理解KM算法后看这篇文章会很有启发:http://www.byvoid.com/blog/tag/%E6%9C%80%E5%A4%A7%E6%9D%83%E5%8C%B9%E9%85%8D/

    由于KM算法不是很好理解,我分享下个人在学习过程中的一些心得:

    为了叙述方便,我们定义二分图上的两个点集分别为U,V,默认U向V寻找最大权匹配。

    很多讲解KM的文章上来就直接提到可行顶标的概念,很容易把人看晕,我们先不去管它。

    先理解KM求最大权匹配的核心思想。其实KM的核心思想很简单,像匈牙利算法那样贪心,先尝试让每个顶点匹配和它相连的权值最大的边。

    但这是几乎不可能的,因为可能有多个最大权边连向同一个待匹配的点,进而可能会影响最后的正确结果。也就是说该贪心策略无法保证没有后效性。

    那么KM的调整策略是:在匹配过程中,如果发现Ui向V寻找最大权边(设为Ei1,次大权边设为Ei2,依次类推……)的端点为Vj,而Vj已经被U集合其他的点(设Ui`)匹配过,则回溯,比较选择(Ei1,Ei'2)和(Ei2,Ei'1),选择较优者,然后继续匹配,如果在回溯过程中遇到同样的冲突,则再向前回溯……,直到找到一个完备匹配,而KM寻找完备匹配的算法就是匈牙利寻找增广路。由于中途可能匹配失败,KM需要进行多次匈牙利算法才能找到一个完备匹配。

    这样的回溯贪心策略最终得到的匹配一定是最优的。以上就是KM的基本思想。

    当点的个数很少时,回溯的代价并不会太大。当点的个数很多,且图很稠密时,贪心回溯会慢慢退化成枚举,效率下降很多。于是KM在执行调整策略的过程中对每个节点引入了一个可行顶标(设U顶标为A,V顶标为B)。对于可行顶标,我觉得可以这样粗略的理解,它是U集合点对V集合点匹配的边权的一个期望,该期望初始为最大,当匹配过程中U中有些点打不到期望,那么就对该期望的值调整……。

    为了保证正确性,在算法执行过程中的任一时刻,对于任一条边(i,j), Ai+Bj>=edge[i,j]始终成立。

    根据顶标调整匹配基于一个正确的定理(详细介绍看上面链接中的资料):

    若由二分图中所有满足A[i]+B[j]=w[i,j]的边(i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。

    这个定理是显然的。因为对于二分图的任意一个匹配,如果它包含于相等子图,那么它的边权和等于所有顶点的顶标和;如果它有的边不包含于相等子图,那么它的边权和小于所有顶点的顶标和。所以相等子图的完备匹配一定是二分图的最大权匹配。

    于是回溯贪心求最大权匹配的过程便等同于维护可行顶标,构造相等子图,求完备匹配的过程了。至于如何维护顶标,以及时间复杂度分析,详细看上面NOCOW链接中的介绍。

    KM算法最精髓也是最难理解的便是维护可行顶标了。

    贴一道求最大权匹配的模板题,URAL 1076

    题目所求就是求矩阵和-最大权匹配,矩阵就是所给二分图。

    由于KM的时间复杂度比较高,只能处理200以内的点,所以适合用邻接矩阵和代码实现较简单的匈牙利算法。

    View Code
     1 #include <iostream>
     2 #include <cstring>
     3 #include <cstdio>
     4 #include <cstdlib>
     5 #include <cmath>
     6 #include <string>
     7 #include <vector>
     8 #include <list>
     9 #include <map>
    10 #include <set>
    11 #include <queue>
    12 #include <stack>
    13 #include <bitset>
    14 #include <algorithm>
    15 #include <numeric>
    16 #include <functional>
    17 using namespace std;
    18 typedef long long ll;
    19 #define inf (int)1e10
    20 #define exp 1e-8
    21 #define read freopen("in.txt","r",stdin)
    22 #define write freopen("out.txt","w",stdout)
    23 #define maxn 150
    24 int edge[maxn][maxn];//邻接矩阵
    25 int du[maxn],dv[maxn];//可行顶标
    26 int head[maxn];//匹配节点的父节点
    27 bool visu[maxn],visv[maxn];//判断是否在交错树上
    28 int uN;//匹配点的个数
    29 int slack[maxn];//松弛数组
    30 bool dfs(int u)
    31 {
    32     visu[u]=true;
    33     for(int v=0;v<uN;v++)
    34         if(!visv[v]){
    35             int t=du[u]+dv[v]-edge[u][v];
    36             if(t==0){
    37                 visv[v]=true;
    38                 if(head[v]==-1||dfs(head[v]))
    39                 {
    40                     head[v]=u;
    41                     return true;
    42                 }
    43             }
    44             else slack[v]=min(slack[v],t);
    45         }
    46     return false;
    47 }
    48 int KM()
    49 {
    50     memset(head,-1,sizeof(head));
    51     memset(du,0,sizeof(du));
    52     memset(dv,0,sizeof(dv));
    53     for(int u=0;u<uN;u++)
    54         for(int v=0;v<uN;v++)
    55             du[u]=max(du[u],edge[u][v]);
    56     for(int u=0;u<uN;u++)
    57     {
    58         for(int i=0;i<uN;i++)slack[i]=inf;
    59         while(true)
    60         {
    61             memset(visu,0,sizeof(visu));
    62             memset(visv,0,sizeof(visv));
    63             if(dfs(u))break;
    64             int ex=inf;
    65             for(int v=0;v<uN;v++)if(!visv[v])
    66                 ex=min(ex,slack[v]);
    67             for(int i=0;i<uN;i++)
    68             {
    69                 if(visu[i])du[i]-=ex;
    70                 if(visv[i])dv[i]+=ex;
    71                 else slack[i]-=ex;
    72 
    73             }
    74         }
    75     }
    76     int ans=0;
    77     for(int u=0;u<uN;u++)
    78         ans+=edge[head[u]][u];
    79     return ans;
    80 }
    81 int main()
    82 {
    83     //read;
    84     while(~scanf("%d",&uN))
    85     {
    86         int sum=0;
    87         for(int i=0;i<uN;i++)
    88             for(int j=0;j<uN;j++)
    89             {
    90                 scanf("%d",&edge[i][j]);
    91                 sum+=edge[i][j];
    92             }
    93         printf("%d\n",sum-KM());
    94     }
    95     return 0;
    96 }
  • 相关阅读:
    BZOJ1033:[ZJOI2008]杀蚂蚁antbuster(模拟)
    BZOJ4001:[TJOI2015]概率论(卡特兰数,概率期望)
    BZOJ1820:[JSOI2010]Express Service 快递服务(DP)
    BZOJ4066:简单题(K-D Tree)
    2110. [NOIP2015普及]金币
    73. 找最佳通路
    cogs 7. 通信线路
    codevs 3295 落单的数
    151. [USACO Dec07] 建造路径
    必备算法之二叉树的相关操作
  • 原文地址:https://www.cnblogs.com/mcflurry/p/2874114.html
Copyright © 2011-2022 走看看