原文博客: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");
}
}