原文博客:http://www.cnblogs.com/-sunshine/archive/2012/08/30/2664242.html
一、匈牙利算法
算法的思路是不停的找增广轨,并增加匹配的个数,增广轨顾名思义是指一条可以使匹配数变多的路径,在匹配问题中,增广轨的表现形式是一条"交错轨",也就是说这条由图的边组成的路径,它的第一条边是目前还没有参与匹配的,第二条边参与了匹配,第三条边没有..最后一条边没有参与匹配,并且始点和终点还没有被选择过.这样交错进行,显然他有奇数条边.那么对于这样一条路径,我们可以将第一条边改为已匹配,第二条边改为未匹配...以此类推.也就是将所有的边进行"反色",容易发现这样修改以后,匹配仍然是合法的,但是匹配数增加了一对.另外,单独的一条连接两个未匹配点的边显然也是交错轨.可以证明,当不能再找到增广轨时,就得到了一个最大匹配.这也就是匈牙利算法的思路.
二、二分图最大匹配
二分图最大匹配的经典匈牙利算法是由Edmonds在1965年提出的,算法的核心就是根据一个初始匹配不停的找增广路,直到没有增广路为止。
匈牙利算法的本质实际上和基于增广路特性的最大流算法还是相似的,只需要注意两点:
(一)每个X节点都最多做一次增广路的起点;
(二)如果一个Y节点已经匹配了,那么增广路到这儿的时候唯一的路径是走到Y节点的匹配点(可以回忆最大流算法中的后向边,这个时候后向边是可以增流的)。
找增广路的时候既可以采用dfs也可以采用bfs,两者都可以保证O(nm)的复杂度,因为每找一条增广路的复杂度是O(m),而最多增广n次,dfs在实际实现中更加简短。
#include <iostream>
#include <cstdio>
#include <cstring>
const int NMAX=100;
bool a[NMAX][NMAX]; //定义n*n数组(n阶方阵)true/false
int edgen,n; //运算时定义的方阵大小n,边的数目edgen
bool visit[NMAX]; //每次search可增广路时,加入的话,visit[j]=true
int mat[NMAX]; //匹配
int cmat;
void intial()
{
std::cout<<"please input two parameter(size,edge_num):"<<std::endl;
std::cin>>n>>edgen;
int x,y;
memset(a,false,sizeof(a)); //学习了
for(int i=1;i<=edgen;++i)
{
scanf("%d%d",&x,&y);
a[x][y]=true;
}
}
bool search(int i)
{
int j;
for(j=1;j<=n;j++)
{
if(a[i][j]&&!visit[j])
{
visit[j]=true;
if(!mat[j]||search(mat[j]))
{
mat[j]=i; //j和最初的i配对,a small question?
return true; //出来
}
}
}
return false;
}
void find()
{
memset(mat,0,sizeof(mat));
for(int i=1;i<=n;i++) //相当于 二分法中的x
{
memset(visit,false,sizeof(visit));
search(i); //对于每个x只能遍历一次(search 可增广路一次)
}
}
void output()
{
int j,cmat=0;
for(j=1;j<n;j++)
if(mat[j])
cmat++;
std::cout<<cmat<<std::endl;
for(j=1;j<n;j++)
if(mat[j])
{
std::cout<<"y="<<j<<" x="<<mat[j]<<std::endl;
}
}
int main()
{
intial();
find();
output();
return 1;
}
三、Hopcroft-Karp算法
SRbGa很早就介绍过这个算法,它可以做到O(sqrt(n)*e)的时间复杂度,并且在实际使用中效果不错而且算法本身并不复杂。
Hopcroft-Karp算法是Hopcroft和Karp在1972年提出的,该算法的主要思想是在每次增广的时候不是找一条增广路而是同时找几条不相交的最短增广路,形成极大增广路集,随后可以沿着这几条增广路同时进行增广。
可以证明在寻找增广路集的每一个阶段所寻找到的最短增广路都具有相等的长度,并且随着算法的进行最短增广路的长度是越来越长的,更进一步的分析可以证明最多只需要增广ceil(sqrt(n))次就可以得到最大匹配(证明在这里略去)。
因此现在的主要难度就是在O(e)的时间复杂度内找到极大最短增广路集,思路并不复杂,首先从所有X的未盖点进行BFS,BFS之后对每个X节点和Y节点维护距离标号,如果Y节点是未盖点那么就找到了一条最短增广路,BFS完之后就找到了最短增广路集,随后可以直接用DFS对所有允许弧(dist[y]=dist[x]+1,可以参见高流推进HLPP的实现)进行类似于匈牙利中寻找增广路的操作,这样就可以做到O(m)的复杂度。
实现起来也并不复杂,对于两边各50000个点,200000条边的二分图最大匹配可以在1s内出解~~
#include <queue>
#include <stdio.h>
#include <string.h>
#include <iostream>
using namespace std;
const int MAXN=3005;
const int INF=1<<28;
bool bmap[MAXN][MAXN];
short a[MAXN][3],b[MAXN][2];
bool bmask[MAXN];
int nx,ny,dis;
int cx[MAXN];
int cy[MAXN];
int dx[MAXN];
int dy[MAXN];
bool searchpath()
{
queue<int> Q;
dis=INF;
memset(dx,-1,sizeof(dx));
memset(dy,-1,sizeof(dy));
for(int i=0;i<nx;i++)
{
if(cx[i]==-1)
{
Q.push(i);
dx[i]=0;
}
}
while(!Q.empty())
{
int u=Q.front();
Q.pop();
if(dx[u]>dis) break;
for(int v=0;v<ny;v++)
{
if(bmap[u][v]&&dy[v]==-1)
{
dy[v]=dx[u]+1;
if(cy[v]==-1) dis=dy[v];
else
{
dx[cy[v]]=dy[v]+1;
Q.push(cy[v]);
}
}
}
}
return dis!=INF;
}
int findpath(int u)
{
for(int v=0;v<ny;v++)
{
if(!bmask[v]&&bmap[u][v]&&dy[v]==dx[u]+1)
{
bmask[v]=1;
if(cy[v]!=-1&&dy[v]==dis)
{
continue;
}
if(cy[v]==-1||findpath(cy[v]))
{
cy[v]=u;cx[u]=v;
return 1;
}
}
}
return 0;
}
void MaxMatch()
{
int res=0;
memset(cx,-1,sizeof(cx));
memset(cy,-1,sizeof(cy));
while(searchpath())
{
memset(bmask,0,sizeof(bmask));
for(int i=0;i<nx;i++)
{
if(cx[i]==-1)
{
res+=findpath(i);
}
}
}
printf("%d\n",res);;
}
int main()
{
int t,T;
scanf("%d",&T);
for(int q=1;q<=T;q++)
{
scanf("%d",&t);
scanf("%d",&nx);
for(int i=0;i<nx;i++)
{
scanf("%d%d%d",&a[i][0],&a[i][1],&a[i][2]);
}
scanf("%d",&ny);
for(int j=0;j<ny;j++)
{
scanf("%d%d",&b[j][0],&b[j][1]);
}
memset(bmap,0,sizeof(bmap));
for(int i=0;i<nx;i++)
for(int j=0;j<ny;j++)
{
if((b[j][0]-a[i][0])*(b[j][0]-a[i][0])+(b[j][1]-a[i][1])*(b[j][1]-a[i][1])<=t*t*a[i][2]*a[i][2])
{
bmap[i][j]=1;
//printf("map[%d][%d]\n",i,j);
}
}
printf("Scenario #%d:\n",q);
MaxMatch();
printf("\n");
}
}