[学习笔记]Hopcroft-karp 算法
壹、模板测试链接
贰、说明
在某个地方看到这种可以在 \(\mathcal O(|E|\sqrt{|V|})\) 时间复杂度以内解决问题的方法,感觉挺有意思的......但是似乎只在无权二分图有用?
\(\tt Hopcroft-Karp\) 算法使用 \(\tt BFS\) 来找出多条不相交的最短增广路,形成极大增广路集,然后用匈牙利多路增广。
\(\tt BFS\) 要找最短的增广路,是因为增广路的长度能增加,他的匹配数也能增加,所以最短保证答案的准确。每一阶段找出来的最短增广路都是相等的长度,后面越来越长。也就是说,每次找出可以达到同样长度的多条增广路。
其实多路增广的思路与最大流的 \(\tt dinic\) 是一样的。
假设我们的图为 \(G=(X,Y,E)\) 算法流程如下:
- 使用 \(\tt BFS\),从尚未匹配的 \(X\) 集中的点出发,并在此过程中得到最短增广路长度;
- 使用匈牙利算法,将所有满足 \(dis\_y_v=dis\_x_u+1\) 的边进行增广;
- 重复 \(1\),直到无法找到增广路;
叁、代码
using namespace Elaina;
const int maxn=500;
const int maxm=5e4;
const int inf=0x3f3f3f3f;
struct edge{int to,nxt;}e[maxm+5];
int tail[maxn+5],ecnt;
int x,y,m;
inline void add_edge(const int u,const int v){
e[++ecnt]=edge{v,tail[u]};tail[u]=ecnt;
}
inline void input(){
x=readin(1),y=readin(1),m=readin(1);
int u,v;
rep(i,1,m){
u=readin(1),v=readin(1);
add_edge(u,v);
}
}
int xtoy[maxn+5],ytox[maxn+5];
int dx[maxn+5],dy[maxn+5];
queue<int>Q;
// the length of the shortest augmented road
int dis;
/** @return whether have found the augmented road*/
inline int bfs(){
dis=inf;
memset(dx+1,-1,x<<2);memset(dy+1,-1,y<<2);
while(!Q.empty())Q.pop();
rep(i,1,x)if(!xtoy[i])Q.push(i),dx[i]=0;
while(!Q.empty()){
int u=Q.front();Q.pop();
// ensure the augmented road is the shortest
if(dx[u]>dis)break;
for(int i=tail[u],v;i;i=e[i].nxt){
v=e[i].to;
if(dy[v]==-1){
dy[v]=dx[u]+1;
// have found the shortest augmented road
if(!ytox[v])dis=dy[v];
else dx[ytox[v]]=dy[v]+1,Q.push(ytox[v]);
}
}
}
return dis!=inf;
}
int vis[maxn+5];
int dfs(const int u){
for(int i=tail[u],v;i;i=e[i].nxt)if(!vis[v=e[i].to]){
if(dy[v]==dx[u]+1){
vis[v]=1;
// length limited
if(ytox[v] && dy[v]==dis)continue;
if(!ytox[v] || dfs(ytox[v])){
ytox[v]=u,xtoy[u]=v;
return 1;
}
}
}
return 0;
}
inline int HK(){
int ret=0;
while(bfs()){
memset(vis+1,0,y<<2);
rep(i,1,x)if(!xtoy[i] && dfs(i))
++ret;
}
return ret;
}
signed main(){
input();
writc(HK(),'\n');
return 0;
}