[学习笔记]二分图匹配与匈牙利算法

前言

今天模拟赛T1二分图匹配板子题,但是我不会,于是就全场就我没AT1系列了,赶紧补坑

算法

主要了解两个概念"交替路","增广路".我们所做的就是不断找增广路.图我太懒不想画...推荐一个我认为写的很好的一篇博客,我就是在这学的

https://www.renfei.org/blog/bipartite-matching.html

定理

  • 最小点覆盖数等于二分图最大匹配数

    证明见Matrix67大神Blog:http://www.matrix67.com/blog/archives/116

  • 最大独立集点数数(点集不包含任何一条边)等于总点数减去二分图最大匹配数

  • DAG上的最小不相交路径覆盖(找出最少的经过顶点各不同的路径覆盖整个图)数等于点数减去最大匹配数

这些还是比较有用的

代码

BFS版本

int pre[maxn],match[maxn],vis[maxn];
inline void hungarian(){
	queue <int>q;
	int s,t,u,v,tmp;
	bool flag=0;
	for(ri i=1;i<=n*2;i++)pre[i]=match[i]=-1,vis[i]=0;
	for(ri o=1;o<=n;o++){
		if(match[o]!=-1)continue;
		while(q.size())q.pop();
		pre[o]=-1,q.push(o),flag=0;
		while(q.size()&&!flag){
			u=q.front();q.pop();
			for(ri i=h[u];i&&!flag;i=edge[i].ne){//注意退出
				v=edge[i].to;
				if(vis[v]==o)continue;
				vis[v]=o;
				if(match[v]!=-1){
					q.push(match[v]);
					pre[match[v]]=u;
				}
				else{
					flag=1;
					s=u,t=v;
					while(s!=-1){
						tmp=match[s];
						match[s]=t,match[t]=s;
						s=pre[s],t=tmp;
					}
				}
			}
		}
		if(match[o]!=-1)ans++;
	}
	printf("%d\n",ans);
	return ;
}

注意几点:

  • 我们找到一条增广路后要及时特判退出

  • 用tmp记录s的另一个匹配

  • 一开始起点pre赋为-1

例(水)题:

JZOJ5934 phalanx

经典模型,行为左边的点集,列为右边的,对于不能染两次的行列连边,容易发现连了边的点一起选是非法的,我们要找的就是选出最多的点集使得没有一条边,转化成最大独立集来做

代码:

const int maxn=4005;
const int inf=0x7fffffff;
struct Edge{
	int ne,to;
}edge[maxn<<2];
int h[maxn<<1],num_edge=1;
inline void add_edge(int f,int to){
	edge[++num_edge].ne=h[f];
	edge[num_edge].to=to;
	h[f]=num_edge;
}
int match[maxn<<1],pre[maxn<<1];
int vis[maxn<<1];
int n,num;
inline void solve(){
	int u,v,s,t,tmp,ans=0;
	bool flag=0;
	queue <int> q;
	for(ri i=1;i<=n*2;i++)vis[i]=0,match[i]=pre[i]=-1;
	for(ri o=1;o<=n;o++){
		if(match[o]==-1){
			while(q.size())q.pop();
			q.push(o);
			pre[o]=-1,flag=0;
			while(q.size()&&!flag){
				u=q.front();q.pop();
				//vis[u]=1;
				for(ri i=h[u];i&&!flag;i=edge[i].ne){
					v=edge[i].to;
					if(vis[v]==o)continue;
					vis[v]=o;
					//printf("--%d %d--\n",u,v);
					if(match[v]!=-1){
						q.push(match[v]);
						pre[match[v]]=u;
					}
					else{
						flag=1;
						s=u,t=v;
						while(s!=-1){
							tmp=match[s];
							match[s]=t,match[t]=s;
							t=tmp,s=pre[s];
						}
					}
				}	
			}
		}
		if(match[o]!=-1)ans++;
	}
	//printf("%d %d\n",n*2,ans);
	printf("%d\n",(n*2-ans)*n);
}
int main(){
	int x,y;
	freopen("phalanx.in","r",stdin);
	freopen("phalanx.out","w",stdout);
	read(n),read(num);
	for(ri i=1;i<=num;i++){
		read(x),read(y);
		add_edge(x,y+n);
		add_edge(y+n,x);
	}
	solve();
	return 0;
}

JZOJ1922 小行星

像上题一样对于小行星所在行列连边,发现任何一条边的左右端点至少选一个,转化成最小点覆盖就好了

代码:

/*
  Code By RyeCatcher
*/
const int maxn=2005;
const int inf=0x7ffffff;
struct Edge{
	int ne,to;
}edge[maxn<<1];
int h[maxn],num_edge=1;
inline void add_edge(int f,int to){
	edge[++num_edge].ne=h[f];
	edge[num_edge].to=to;
	h[f]=num_edge;
}
int n,k,ans=0;
int pre[maxn],match[maxn],vis[maxn];
inline void hungarian(){
	queue <int>q;
	int s,t,u,v,tmp;
	bool flag=0;
	for(ri i=1;i<=n*2;i++)pre[i]=match[i]=-1,vis[i]=0;
	for(ri o=1;o<=n;o++){
		if(match[o]!=-1)continue;
		while(q.size())q.pop();
		pre[o]=-1,q.push(o),flag=0;
		while(q.size()&&!flag){
			u=q.front();q.pop();
			for(ri i=h[u];i&&!flag;i=edge[i].ne){
				v=edge[i].to;
				if(vis[v]==o)continue;
				vis[v]=o;
				if(match[v]!=-1){
					q.push(match[v]);
					pre[match[v]]=u;
				}
				else{
					flag=1;
					s=u,t=v;
					while(s!=-1){
						tmp=match[s];
						match[s]=t,match[t]=s;
						s=pre[s],t=tmp;
					}
				}
			}
		}
		if(match[o]!=-1)ans++;
	}
	printf("%d\n",ans);
	return ;
}
int main(){
	read(n),read(k);
	int x,y;
	for(ri i=1;i<=k;i++){
		read(x),read(y);
		add_edge(x,n+y);
		add_edge(n+y,x);
	}
	hungarian();
	return 0;
}
posted @ 2018-10-29 23:56  Rye_Catcher  阅读(157)  评论(1编辑  收藏  举报