[ACM]二分图 最大匹配 最大权匹配 匈牙利 Hopcroft–Karp Kuhn-Munkres

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

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 }

 

 

posted @ 2013-01-24 00:29  McFlurry  阅读(1701)  评论(0编辑  收藏  举报