这次我写的博客跟上次的BIT是一样的,都是难度系数比较高的算法,在NOIP中很少用到,但是也不是用不到,如果有兴趣的读者可以自己来看一下,这次的这一篇呢,是关于二分图匹配的,如果想做题的话,我推荐codevs 1022 覆盖,前提是必须熟练掌握匈牙利算法。至于codevs 1232 飞行员配对呢,我建议不要去做,当一个练手的题就好了,因为那个题需要Special Judge,而出题人比较懒,没有写,所以通过率会出现极低的情况,所以我不建议死做那个题,只需要练练手即可。
那么废话不多说,开始这次的对于二分图求最大匹配的匈牙利算法的讲解:
其实有些配对问题都可以看成二分图,就是一边对着另一边,找最多能配多少个对(顶多也就是加一些细节处理和配对输出就是了),下面来讲几个概念:
设图M为图G的一个匹配:
1.交错路:p是G的一条通路,如果p中的边为属于M中的边与不属于M但属于G中的边交替出现,则称p是一条交错路;
2.未覆盖点:这个很好理解,就是还没有成功配对的点;
3.可增广路:如果p中有一条边<u,v>满足u和v都是未覆盖点,则称这条路径为可增广路。
显而易见,大家能想到的思路就是一遍一遍的暴力搜索,然后保留匹配数最大的那一种。但是大家有没有想过,这种算法的效率能高么。事实证明这样的时间复杂度,是以边数的指数来的,所以效率会很低。那么应该怎么办呢?那么下面我们就来学习一下匈牙利算法。
首先我们看到1和4能匹配,1和5也能匹配,那么我们先选择1和5匹配(为了讲解一下匈牙利算法……),然后我们在找到2号节点,可以发现,这时候2已经不能和5匹配,于是选择与6匹配;
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <cmath> 5 using namespace std; 6 int n,m,a,b,tot = 0; 7 bool map[101][101];//记录二分图中两边是否有边相连 8 bool state[101];//记录二分图右半部分在匹配中是否已经选择 9 int result[101];//记录二分图右半部分在匹配完成后,左半部分那个节点与它配对,为0的话则说明未配对成功 10 bool find(int x){ 11 for(int i = 1;i <= n;++i){ 12 if(map[x][i] && !state[i]){ 13 state[i] = true;//标记在本次查找中已经查找 14 if(result[i] == 0 || find(result[i])){ 15 result[i] = x; 16 return true; 17 } 18 } 19 } 20 return false; 21 } 22 void hungary(){ 23 for(int i = 1;i <= n;++i){ 24 memset(state,0,sizeof(state));//bool中的false = 0; 25 if(find(i))tot++; 26 } 27 } 28 int main(){ 29 memset(result,0,sizeof(result)); 30 memset(map,0,sizeof(map)); 31 cin >> n >> m;//缩进是个好习惯; 32 for(int i = 1;i <= m;++i){ 33 cin >> a >> b; 34 map[a][b] = true; 35 } 36 hungary(); 37 cout << tot << endl; 38 for(int i = 1;i <= n;++i){ 39 if(result[i] != 0) cout << result[i] << "->" << i << endl; 40 } 41 return 0; 42 }
大家可以看一下。
下面给出邻接表的匈牙利算法代码(不一定二分图两边的节点编号一样,我使用了STL中的vector模版):
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <cmath> 5 #include <cstring> 6 #include <vector> 7 #define maxn 101 8 using namespace std; 9 vector<int>edges[maxn]; 10 bool state[maxn]; 11 int result[maxn]; 12 int n,m,a,b,tot = 0; 13 bool find(int x){ 14 for(int i = 0;i < edges[x].size();++i){ 15 int o = edges[x][i]; 16 if(!state[o]){ 17 state[o] = true; 18 if(result[o] == 0 || find(o)){ 19 result[o] = x; 20 return true; 21 } 22 } 23 } 24 return false; 25 } 26 void hungary(){ 27 for(int i = 1;i <= n;++i){ 28 memset(state,0,sizeof(state)); 29 if(find(i))tot++; 30 } 31 } 32 int main(){ 33 cin >> n >> m; 34 for(int i = 1;i <= m;++i){ 35 cin >> a >> b; 36 edges[a].push_back(b); 37 } 38 hungary(); 39 cout << tot << endl; 40 for(int i = 1;i <= n;++i){ 41 if(result[i] != 0) cout << result[i] << "->" << i << endl; 42 } 43 return 0; 44 }
这次的二分图求最大匹配——匈牙利算法就讲到这里。注意,这里的增广路和网络流的增广路不要弄混,以后我会在博客中写一些网络流的东西,尤其是网络流的增广路算法。敬请期待。