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


    前言

    今天模拟赛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
    ",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--
    ",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*2,ans);
    	printf("%d
    ",(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
    ",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;
    }
    
  • 相关阅读:
    将一个 LIB 中的所有 OBJ 名称和类型输出到一个物理文件
    用一条命令 转换 Iseries tables t到 CSV
    用CL提取系统值
    程式中檢查是否潤年的新方法
    Ubuntu 忘记root密码 和修改密码
    excel 数组公式入门
    BAT批处理
    row_number() over(partition byCOL1 order by COL2)
    row_number( )、rank( )、dense_rank( )、ntile( )函数的用法 (SQL2005以上)
    Sql 常用日期计算
  • 原文地址:https://www.cnblogs.com/Rye-Catcher/p/9874146.html
Copyright © 2020-2023  润新知