二分图求最大匹配的最常用算法是匈牙利算法,匈牙利算法的实质与求最大流的思想一致,通过寻求增光路来扩大匹配的个数,这里不详细介绍。
König定理,求解二分图非常重要的一个定理,简洁的说就是:二分图中的最大匹配数等于这个图中的最小点覆盖数,因此求最大匹配和最小点覆盖是相辅相成的,证明这里不介绍。
要详细看匈牙利算法的介绍到这:http://www.byvoid.com/blog/hungary/
要详细理解König定理到这:http://www.matrix67.com/blog/archives/116
求解匹配问题的主要障碍是构图,至于怎么构图,这个需要自己做题的积累,我也在摸索中……,以后若有心得会更新进来。
只要将原问题抽象成二分图的匹配问题,且已构建好图,那么编写代码就没有难度了,因为匈牙利算法很好实现。
求最大匹配的一个例题,POJ 3020 Antenna Placement
题意求最小圈个数,使得所有的点被圈起来,圈的范围是上下或左右两个。此题若将每个点一拆为2,那么就可以抽象成一个最大匹配问题,点的个数-最大匹配就是答案,由于加入了与原先相同的一张图,两边都可以匹配对方,所以求得的匹配数是原图的2倍。

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的原因同上。

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

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次。

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以内的点,所以适合用邻接矩阵和代码实现较简单的匈牙利算法。

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 }