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 }
  • 相关阅读:
    LeetCode 230. Kth Smallest Element in a BST
    LeetCode 114. Flatten Binary Tree to Linked List
    LeetCode 222. Count Complete Tree Nodes
    LeetCode 129. Sum Root to Leaf Numbers
    LeetCode 113. Path Sum II
    LeetCode 257. Binary Tree Paths
    Java Convert String & Int
    Java Annotations
    LeetCode 236. Lowest Common Ancestor of a Binary Tree
    LeetCode 235. Lowest Common Ancestor of a Binary Search Tree
  • 原文地址:https://www.cnblogs.com/mcflurry/p/2874114.html
Copyright © 2011-2022 走看看