zoukankan      html  css  js  c++  java
  • 二分图匹配例题及拓展

    例题#

    洛谷P1894

    分析:##

    裸题,牛栏作为一个点集,牛作为另一个,牛喜欢牛栏则从牛向牛栏连一条边跑匈牙利就得了,邻接表开大点

    代码:##

    #include<bits/stdc++.h>
    #define MAXN (2000+5)
    using namespace std;
    inline int read(){
    	int cnt=0,f=1;char c;
    	c=getchar();
    	while(!isdigit(c)){
    		if(c=='-')f=-f;
    		c=getchar();
    	}
    	while(isdigit(c)){
    		cnt=cnt*10+c-'0';
    		c=getchar();
    	}
    	return cnt*f;
    }
    int n,m,nxt[2*MAXN],first[2*MAXN],to[2*MAXN],match[2*MAXN],x,y,ans,tot,res;
    bool vis[2*MAXN];
    void add(int x,int y){
    	nxt[++tot]=first[x];
    	first[x]=tot;
    	to[tot]=y;
    }
    int find(int u){
    	for(register int i=first[u];i;i=nxt[i]){
    		int v=to[i];
    		if(vis[v])continue;
    		else {
    			vis[v]=true;
    			if(match[v]==-1||find(match[v])) {
    				match[v]=u;
    				match[u]=v;
    				return 1;
    			}
    		}
    	}
    	return 0;
    }
    int hungary(){
    	for(register int i=1;i<=n;i++){
    		for(register int j=1;j<=2*n+1;j++)vis[j]=false;
    		if(match[i]==-1)ans+=find(i);
    	}
    	return ans;
    }
    int main(){
    	n=read();m=read();
    	for(register int i=1;i<=n+m;i++)match[i]=-1;
    	for(register int i=1;i<=n;i++){
    		x=read();
    		for(register int j=1;j<=x;j++){
    			y=read();add(i,y+n);
    		}
    	}
    		
    	printf("%d",hungary());
    	return 0;
    }
    

    题目链接:##

    洛谷P1129

    分析:##

    行和列分别作为两边的点,将原图的点作为边建图
    因为行和列的交换并不会改变图的形态而只是交换编号,所以对最终答案没有影响
    所以读入图的时候如果Map[i,j]1就从i往j连一条边
    然后跑最大匹配就可以了,跑完若ans
    n则说明所有行和列都有对应匹配,那么说明有方案,如果ans<n说明不是所有的行和列都能有对应匹配,所以就不能通过变换达成对角线(每行每列都有匹配且不和其他行/列的匹配冲突),说明没有方案
    多组数据,记得初始化

    代码:##

    #include<bits/stdc++.h>
    #define N (20000+5)
    #define M (200000+5)
    using namespace std;
    inline int read(){
        int cnt=0,f=1;char c;
        c=getchar();
        while(!isdigit(c)){
            if(c=='-')f=-f;
            c=getchar();
        }
        while(isdigit(c)){
            cnt=cnt*10+c-'0';
            c=getchar();
        }
        return cnt*f;
    }
    int n,t,nxt[M],first[N],to[M],x,match[N],tot,res,ans;
    bool vis[N];
    void add(int x,int y){
        nxt[++tot]=first[x];
        first[x]=tot;
        to[tot]=y;
    }
    int find(int u){
        for(register int i=first[u];i;i=nxt[i]){
            int v=to[i];
            if(vis[v]) continue;
            else {
                vis[v]=true;
                if(match[v]==-1||find(match[v])){
                    match[v]=u;
                    return 1;
                }
            }
        }
        return 0;
    }
    int hungary(){
        for(register int i=1;i<=n;i++) {
            for(register int j=1;j<=n;j++)vis[j]=false;
            ans+=find(i);
        }
        return ans;
    }
    int main(){
        t=read();
        while(t--) {
            n=read();
            for(register int i=1;i<=n;i++)match[i]=-1;
            
            for(register int i=1;i<=n;i++)
                for(register int j=1;j<=n;j++) {
                    x=read();if(x)add(i,j);
                }
                
            res=hungary();
            if(res==n)printf("Yes
    ");
            else printf("No
    ");
            for(register int i=1;i<=n;i++) first[i]=0;
            for(register int i=1;i<=tot;i++) nxt[i]=to[i]=0;
            tot=0;res=0;ans=0;
        }
        return 0;
    }
    

    另外上面代码的初始化非常猥琐……memset太慢了,所以自己根据n的大小来初始化的不过并没有快多少


    题目链接:##

    POJ2446

    分析:##

    由于每张纸片是(1*2)的,并且每张纸片覆盖的两个格子的坐标((i,j))必有((i+j))奇偶性不相同,所以考虑以((i+j))的奇偶性建立两个点集,然后只要没洞/没越界都可以把相邻两个格子代表的点连上一条边,跑出最大匹配(M),如果(M*2)=(n*m-k)则说明有方案,反之则无。

    代码:##

    #include<bits/stdc++.h>
    #define N 500
    #define M 10000
    using namespace std;
    inline int read(){
    	int cnt=0,f=1;char c;
    	c=getchar();
    	while(!isdigit(c)){
    		if(c=='-')f=-f;
    		c=getchar();
    	}
    	while(isdigit(c)){
    		cnt=cnt*10+c-'0';
    		c=getchar();
    	}
    	return cnt*f;
    }
    int n,m,t,Map[N][N],color[N][N],white=0,black=0,x,y,nxt[M],first[N],to[M],ans,tot,match[N];
    bool vis[N];
    void add(int x,int y){
    	nxt[++tot]=first[x];
    	first[x]=tot;
    	to[tot]=y;
    }
    void build_map(){
    	/*------------------染色,编号------------------*/ 
    	for(register int i=1;i<=n;i++)
    		for(register int j=1;j<=m;j++){
    			if((i+j)%2)color[i][j]=++white;
    			else color[i][j]=++black;
    		}
    	/*------------------染色,编号------------------*/
    	
    	/*--------------------连边----------------------*/ 
    	for(register int i=1;i<=n;i++)
    		for(register int j=1;j<=m;j++)
    			if(!Map[i][j]&&(i+j)%2){
    				if(i-1>0&&!Map[i-1][j])add(color[i][j],color[i-1][j]);  //上 
    				if(j-1>0&&!Map[i][j-1])add(color[i][j],color[i][j-1]);  //左
    				if(i+1<=n&&!Map[i+1][j])add(color[i][j],color[i+1][j]); //下 
    				if(j+1<=m&&!Map[i][j+1])add(color[i][j],color[i][j+1]); //右
    			}
    	/*--------------------连边----------------------*/ 
    	
    }
    int find(int u){
    	for(register int i=first[u];i;i=nxt[i]){
    		int v=to[i];
    		if(vis[v])continue;
    		else {
    			vis[v]=1;
    			if(match[v]==-1||find(match[v])){
    				match[v]=u;
    				return 1;
    			}
    		}
    	}
    	return 0;
    }
    int hungary(){
    	for(register int i=1;i<=white;i++){
    		for(register int j=1;j<=black;j++)vis[j]=0;
    		ans+=find(i);
    	}
    	return ans;
    }
    int main(){
    	n=read();m=read();t=read();
    	for(register int i=1;i<=t;i++) { x=read();y=read();Map[y][x]=1; }   /*这里巨坑……输入的是坐标形式的(x,y)所以要反过来打标记*/ 
    	build_map();
    	for(register int i=1;i<=black;i++)match[i]=-1;
    	int res=hungary();
    //	cout<<res;return 0;
    	if(res*2==n*m-t)printf("YES");
    	else printf("NO");
    	return 0;
    }
    

    拓展#

    1 最小点覆盖##

    最小点覆盖要求用最少的点(任意集合均可)让每条边都至少与一个点相关联。
    说人话就是在二分图里面选一些点出来,让每个边都有至少一个端点。
    可以证明最小点覆盖=最大匹配(M)
    简要证明如下:

    • (M)个是充分的:将这些点覆盖(M)条匹配边,则其他边也一定被覆盖。若有其他边未被覆盖,则将此边加入可得到一个更大的匹配(M'),与(M)为最大匹配矛盾。
    • (M)个是必要的:仅考虑这(M)条匹配边,根据最大匹配的定义可以知道,由于这(M)条匹配边没有公共点,所以至少需要(M)个顶点将其全部覆盖。

    例题:###

    POJ3041 Asteroids

    题意:###

    一个平面内给出一些障碍物,每次操作可以消除整行或整列的障碍物,问最少需要操作多少次?

    分析:###

    这道题可以将行和列转化成二分图里的两个点集,而障碍物作为边的形式存在,若障碍物在(i)(j)列,则从(i)(j)连一条边。一次消除一行或一列的操作可以看做是选一个点,而这个点所覆盖的边(即障碍物)将被“清除”,于是问题转化为求二分图的最小点覆盖,最小点覆盖=最大匹配数,跑匈牙利即可。
    事实上,有不少问题也采用了类似的转化方式(即若题目所给的一条信息中有两个特征,那么将这两个特征分别作为(X)点集和(Y)点集中的点,而其信息本身作为边),但做题时切不能生搬硬套。

    代码:###

    #include<bits/stdc++.h>
    #define N (40000+5)
    #define M (200000+5)
    using namespace std;
    inline int read(){
    	int cnt=0,f=1;char c;
    	c=getchar();
    	while(!isdigit(c)){
    		if(c=='-')f=-f;
    		c=getchar();
    	}
    	while(isdigit(c)){
    		cnt=cnt*10+c-'0';
    		c=getchar();
    	}
    	return cnt*f;
    }
    int n,m,x,y;
    int nxt[M],first[N],to[M],match[N],ans,tot;
    bool vis[N];
    void add(int x,int y){
    	nxt[++tot]=first[x];
    	first[x]=tot;
    	to[tot]=y;
    }
    int find(int u){
    	for(register int i=first[u];i;i=nxt[i]) {
    		int v=to[i];
    		if(vis[v]) continue;
    		else {
    			vis[v]=true;
    			if(match[v]==-1||find(match[v])) {
    				match[v]=u;
    				return 1;
    			}
    		}
    	}
    	return 0;
    }
    
    int hungary() {
    	for(register int i=1;i<=m;i++) match[i]=-1;
    	for(register int i=1;i<=n;i++){
    		for(register int j=1;j<=m;j++) vis[j]=false;
    		ans+=find(i);
    	}
    	
    	return ans;
    }
    int main(){
    	n=read();m=read();
    	for(register int i=1;i<=m;i++) {
    		x=read();y=read();add(x,y);
    	}
    	printf("%d",hungary());
    	return 0;
    }
    

    话说这个题为什么初始化(vis)数组的时候把(j)打成(i)了还有64啊……


    2 最大独立集##

    最大独立集:在包含N个点的图G中选出m个点,使得这些点之间两两没有连边,当m取得最大值时称这些选出的点集为图G的最大独立集
    对于二分图中的最大独立集有一个重要结论:
    二分图中的最大独立集=节点数-最小覆盖点数
    简要证明一下:
    在一个二分图G中,每一条边中都至少有一个顶点在G的覆盖集中,所以对于每条边上的未选点,其对面的点都在覆盖集中,所以剩下的点两两不会有连边,这就是一个独立集。此时这个独立集与覆盖集互补。于是有最大独立集与最小覆盖点集互补。
    有了以上结论,结合“最小覆盖点=最大匹配”可以得到最大独立集=节点数-最大匹配

    例题:###


    WOJ上的,学校电脑不方便粘网址……

    分析:###

    感觉这个题很毒啊,数据真的保证了男女配对不冲突吗
    要是来一组
    3 3
    1 2
    2 3
    3 1
    那不就GG了吗……1和3明显gay住了啊
    然后我就开始手动编号,先无脑连边建个乱七八糟不知道是什么的图再递归染色,写了一个多小时尝试各种姿势建图,然后在WOJ上WA成了傻逼……后来去问了下梁老关于这个题数据的问题,梁老表示这个题数据好像是有点锅,唔那就不管了吧,无脑连边水个AC
    没错我在AC面前屈服了

    代码:###

    并没有大锅但是GG了的代码长这样:####

    #include<bits/stdc++.h>
    #define N 10000
    #define M 20000
    using namespace std;
    inline int read(){
    	int cnt=0,f=1;char c;
    	c=getchar();
    	while(!isdigit(c)){
    		if(c=='-')f=-f;
    		c=getchar();
    	}
    	while(isdigit(c)){
    		cnt=cnt*10+c-'0';
    		c=getchar();
    	}
    	return cnt*f;
    }
    int n,m,x,y,nxt[M],first[N],to[M],tot,match[N],ans=0,a[N],b[N];
    int belongs[N];
    bool vis[N],built[N]; 
    void add(int x,int y){
    	nxt[++tot]=first[x];
    	first[x]=tot;
    	to[tot]=y;
    }
    void build_map(int u){
    	built[u]=1;
    	if(belongs[u]==-1)belongs[u]=1;
    	for(register int i=first[u];i;i=nxt[i]){
    		int v=to[i];
    		belongs[v]=1-belongs[u];
    //		cout<<"v="<<v<<" belongsto"<<belongs[v]<<endl;
    		build_map(v);
    	}
    	return;
    }
    int find(int u){
    	for(register int i=first[u];i;i=nxt[i]){
    		int v=to[i];
    		if(vis[v]) continue;
    		else {
    			vis[v]=true;
    			if(match[v]==-1||find(match[v])){
    				match[v]=u;
    				return 1;
    			}
    		}
    	}
    	return 0;
    }
    int hungary(){
    	for(register int i=1;i<=n;i++)match[i]=-1;
    	for(register int i=1;i<=n;i++){
    		for(register int j=1;j<=n;j++) vis[j]=false;
    		if(belongs[i]==1)ans+=find(i);
    	}
    	return ans;
    }
    int main(){
    	n=read();m=read();
    	for(register int i=1;i<=n;i++) belongs[i]=-1,built[i]=0; 
    	for(register int i=1;i<=m;i++){
    		a[i]=read();b[i]=read();
    		a[i]++;b[i]++;add(a[i],b[i]);
    	}
    //	belongs[a[1]]=1;
    //	for(register int i=1;i<=m;i++)cout<<"a[i]="<<a[i]<<" b[i]="<<b[i]<<endl;
    	for(register int i=1;i<=m;i++)
    		if(!built[a[i]]) build_map(a[i]);
    	
    	for(register int i=1;i<=tot;i++)nxt[i]=to[i]=0;
    	for(register int i=1;i<=n;i++)first[i]=0;
    	tot=0;
    	for(register int i=1;i<=n;i++){
    		if(belongs[a[i]]==1)add(a[i],b[i]);
    		if(belongs[b[i]]==1)add(b[i],a[i]);
    	}
    //	for(register int i=1;i<=n;i++)cout<<belongs[i];
    	printf("%d",n-hungary());
    	return 0;
    }
    

    AC代码,不要把这玩意当成上面例题的题解,当个求最大独立集板子用……####

    #include<bits/stdc++.h>
    #define N 10000
    #define M 20000
    using namespace std;
    inline int read(){
    	int cnt=0,f=1;char c;
    	c=getchar();
    	while(!isdigit(c)){
    		if(c=='-')f=-f;
    		c=getchar();
    	}
    	while(isdigit(c)){
    		cnt=cnt*10+c-'0';
    		c=getchar();
    	}
    	return cnt*f;
    }
    int n,m,x,y,nxt[M],first[N],to[M],tot,match[N],ans=0;
    bool vis[N],built[N]; 
    void add(int x,int y){
    	nxt[++tot]=first[x];
    	first[x]=tot;
    	to[tot]=y;
    }
    
    int find(int u){
    	for(register int i=first[u];i;i=nxt[i]){
    		int v=to[i];
    		if(vis[v]) continue;
    		else {
    			vis[v]=true;
    			if(match[v]==-1||find(match[v])){
    				match[v]=u;
    				return 1;
    			}
    		}
    	}
    	return 0;
    }
    int hungary(){
    	for(register int i=1;i<=n;i++)match[i]=-1;
    	for(register int i=1;i<=n;i++){
    		for(register int j=1;j<=n;j++) vis[j]=false;
    		ans+=find(i);
    	}
    	return ans;
    }
    int main(){
    	n=read();m=read();
    	for(register int i=1;i<=m;i++){
    		x=read();y=read();
    		x++;y++;add(x,y);
    	}
    //	for(register int i=1;i<=n;i++)cout<<belongs[i];
    	printf("%d",n-hungary());
    	return 0;
    }
    
    

    3 最小边覆盖##

    边覆盖集:在图G中选出一些边,构成的能使得G中所有顶点都在某条边上的边集。
    最小边覆盖:所有的边覆盖集中最小的一个集合。
    最小边覆盖=最大独立集=n-最大匹配

  • 相关阅读:
    web性能优化
    5、Git:使用码云(Gitee)
    4、Git:文件操作
    3、Git:基本理论 和 项目搭建
    2、Git:环境配置
    1、Git:版本控制 和 Git历史
    18、各种锁的理解(非公平锁和公平锁、可重入锁、自旋锁、死锁)
    17、原子引用(乐观锁)
    16、深入理解CAS(重点)
    15、彻底玩转单例模式
  • 原文地址:https://www.cnblogs.com/kma093/p/10312475.html
Copyright © 2011-2022 走看看