二分图就是把图的点集分成X和Y两个集合且连接每条边的两个顶点分别在两个集合。
匹配是指图的边集的一个子集,其中任意两条边没有相同的顶点。
最大匹配就是找到一个匹配使得匹配里面的边数最多。
增广路径就是指从图里面一个还未匹配的点开始,走到一条未匹配边,又经过一条匹配过的边,然后又经过一条未匹配过的边,这样每次交替经过未匹配和匹配的边最后到达一个未匹配的点的一条路径。增广路径有重要的性质:1、增广路一定有奇数条边。 2、增广路中未匹配边一定比匹配边多一条(因为是从未匹配点出发走交错路到未匹配点结束)
由于增广路中未匹配的边比匹配的边多一条,那么把匹配的边和未匹配的边互换一下就会增加一条匹配的边,这就是匈牙利算法的核心思想。
从X集合中找到一个未匹配点,寻找增广路,找到了,匹配数+1,如果没有找到,那么从X中找到下一个未匹配的点,再次寻找增广路,重复上述过程,直到X集合中的所有节点都被“增广”完毕,无论如何都找不到增广路,那么整个图的匹配数就最大了。
代码模板:
1 /* 2 把二分图左右分别集合X和Y,X中有元素n个,Y中有元素m个。 3 每次从X集合中的元素开始寻找增广路,最大匹配就是最后一共有多少条增广路。 4 寻找增广路的过程是个递归的过程,它会一层一层找到底。 5 */ 6 7 int match[MAX][MAX];//match[i][j]=1表示X中的i与Y中的j有连边,match[i][j]=0表示无连边 8 int cx[MAX],cy[MAX];//cx[i]表示与X中i匹配的Y中顶点编号,cy[j]表示与Y中j匹配的x中顶点编号 9 bool vis[MAX];//每次寻找增广路时的访问数组标记,为1表示已经访问过,为0表示没有。10 11 bool dfs(int u) //如果能够找到一条从u开始的增广路则返回1,否则返回012 {13 for(int v=1;v<=m;v++){ //循环Y中的m个元素v14 if(!vis[v]&&match[u][v]){ //如果v没有访问过而且与u有连边15 vis[v]=1; //那么就访问v16 if(cy[v]==-1||dfs(cy[v])){ //如果还没有与v匹配的边或者有匹配的边但能够从v找到一条增广路17 cy[v]=u; //那么就把u置为与v匹配的X中的点18 return 1; //此时找到了增广路,返回true19 }20 }21 }22 return 0; //循环完了都没找到,返回false23 }24 25 26 int main()27 {28 int ans=0;//最大匹配数目29 for(int i=1;i<=n;i++){ //循环X中的n个元素,寻找匹配30 memset(vis,0,sizeof(vis)); //每次循环都要重置访问标记数组31 if (dfs(i)) ans++; //增广路数增加32 }33 cout<<
举个栗子:
题意就是有M个女生,N个男生,过山车只能女生男生配对一起坐,而女生只会挑跟想在一起的男生坐过山车,给出女生的K个条件(选择哪个男生),就总共能有多少对女生男生一起坐过山车。
代码:
1 #include2 #include 3 #include 4 #include 5 using namespace std; 6 const int maxn=505; 7 int match[maxn][maxn]; 8 int cy[maxn]; 9 bool vis[maxn];10 int K,N,M;11 12 bool dfs(int u)13 {14 for(int v=1;v<=M;v++){15 if(!vis[v]&&match[v][u]){16 vis[v]=1;17 if(cy[v]==-1||dfs(cy[v])){18 cy[v]=u;19 return 1;20 }21 }22 }23 return 0;24 }25 26 27 int main()28 {29 while(cin>>K)30 {31 if(!K) break;32 cin>>M>>N;33 memset(match,0,sizeof(match));34 memset(cy,-1,sizeof(cy));35 int ans=0,x,y;36 for(int i=0;i >x>>y;38 match[x][y]=1;39 }40 for(int i=1;i<=N;i++){41 memset(vis,0,sizeof(vis));42 if(dfs(i)) ans++;43 }44 cout< <