zoukankan      html  css  js  c++  java
  • 二分图学习笔记

    震惊,我发现我居然不会二分图,兼职或到薄
    于是看了李煜东老师的蓝书,补了补锅。

    很多时候根本看不出来某道题是二分图,因此学习一下建模的技巧。
    首先是二分图最大匹配。

    蓝书原话:

    二分图匹配的模型有两个要素:

    1.节点能分成独立的两个集合,每个集合内部有0条边。(0要素)

    2.每个节点只能与1条匹配边相连。(1要素)

    说的竟然折磨好,简直*****

    首先是这个题。一种比较常见的模型是网格里面放东西,大概每行每列只能放一个。这时可以把每一行和每一列分别抽象成一个点集。”在当前坐标放置机器人“相当于连接该行该列的一条边。匹配的性质:任意两条边都没有公共端点,在本题即为任意两个机器人没有放在同一行或同一列上。因此找到最大匹配即为最终答案。
    具体实现的话,因为有墙的存在,打破了”每行每列只能放一个“的性质。因此可以考虑把行和列重新标号,把在同一列(行)的两个墙中间的格子编为一行(列)。这样就能满足性质了。

    代码:

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=50+10,maxm=2500+10;
    char c[maxn][maxn];
    int n,m,cnt,cnth,cntl,tot,ans;
    struct Node{
    	int x,y;
    }a[maxn][maxn];
    int head[maxm],match[maxm],vis[maxm];
    struct node{
    	int to,next;
    }edge[maxm<<1];
    void add(int from,int to){
    	edge[++cnt].to=to;
    	edge[cnt].next=head[from];
    	head[from]=cnt;
    }
    bool find(int u){
    	for(int i=head[u];i;i=edge[i].next){
    		int v=edge[i].to;
    		if(!vis[v]){
    			vis[v]=1;
    			if(!match[v]||find(match[v])){
    				match[v]=u;
    				return true;
    			}
    		}
    	}
    	return false;
    }
    void Solve(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i) scanf("%s",c[i]+1);
    	cnth=cntl=1;
    	for(int i=1;i<=n;++i){
    		for(int j=1;j<=m;++j){
    			if(c[i][j]=='o') a[i][j].x=cnth;
    			else if(c[i][j]=='#'&&j!=m) cnth++;
    		}
    		cnth++;
    	}
    	for(int j=1;j<=m;++j){
    		for(int i=1;i<=n;++i){
    			if(c[i][j]=='o') a[i][j].y=cntl;
    			else if(c[i][j]=='#'&&i!=n) cntl++;
    		}
    		cntl++;
    	}
    	tot=cntl+cnth;
    	for(int i=1;i<=n;++i){
    		for(int j=1;j<=m;++j){
    			if(c[i][j]=='o'){
    				add(a[i][j].x,a[i][j].y+cnth);
    				add(a[i][j].y+cnth,a[i][j].x);
    			}
    		}
    	}
    	for(int i=1;i<=tot;++i){
    		memset(vis,0,sizeof(vis));
    		if(find(i)) ans++;
    	}
    	printf("%d",ans/2);
    }
    int main(){
    	freopen("robots.in","r",stdin);
    	freopen("robots.out","w",stdout);
    	Solve();
    	return 0;
    }
    

    然后是这个:

    我们用蓝书的思想来套一下这道题:
    分别把人和传送点抽象成一个点集,这两个集合之间显然没有连边。满足”0要素“。而每个人只会进入一个传送门,每个传送门又都只会被一个人进入。满足”1要素“。
    连边跑匈牙利即可。
    然后取得了71分的好成绩?

    我一看



    不 愧 是 我
    AC代码:

    
    
    #include <bits/stdc++.h>
    using namespace std;
    const int maxn=20000+10;
    int r,a,cnt,ans;
    struct node{
    	double x,y;
    }d[maxn],p[maxn];
    int head[maxn<<1],match[maxn<<1];
    bool vis[maxn<<1];
    double t;
    struct Node{
    	int to,next;
    }edge[maxn<<2];
    void add(int from,int to){
    	edge[++cnt].to=to;
    	edge[cnt].next=head[from];
    	head[from]=cnt;
    }
    bool find(int u){
    	for(int i=head[u];i;i=edge[i].next){
    		int v=edge[i].to;
    		if(!vis[v]){
    			vis[v]=1;
    			if(!match[v]||find(match[v])){
    				match[v]=u;
    				return true;
    			}
    		}
    	}
    	return false;
    }
    void Solve(){
    	scanf("%d%d%lf",&r,&a,&t);
    	for(int i=1;i<=a;++i) scanf("%lf%lf",&d[i].x,&d[i].y);
    	for(int i=1;i<=r;++i){
    		double A,B,v;
    		scanf("%lf%lf%lf",&A,&B,&v);
    		for(int j=1;j<=a;++j){
    			double dis=sqrt((A-d[j].x)*(A-d[j].x)+(B-d[j].y)*(B-d[j].y));
    			if(v*t>=dis){
    				add(i+a,j);
    				add(j,i+a);
    			}
    		}
    	}
    	for(int i=1;i<=a+r;++i){
    		memset(vis,0,sizeof(vis));
    		if(find(i)) ans++;
    	}
    	printf("%d",ans/2);
    }
    int main(){
    	Solve();
    	return 0;
    }
    
    

    然后是这个:刷题的事能叫水吗

    最板子的一道题,我竟然没有一眼看出来。
    首先每行每列只会被”冲“一次,每个点只会被冲一次,满足”1要素“。
    其次行与行之间,列与列之间没有任何关系。
    因此类似robots的做法,跑一个最大匹配即可。

    概念:最小点覆盖:给定一张二分图,求出一个最小的点集S,使得图中任意一条边都有至少一个端点属于S。

    Konig定理:最大匹配数 = 最小点覆盖数

    有了这个定理就能够解决这个题辣!
    代码: 代码的事怎么能叫水篇幅呢

    #include <bits/stdc++.h>
    using namespace std;
    const int maxm=10000+10,maxn=1000+10;
    int head[maxn],vis[maxn],match[maxn];
    struct node{
    	int to,next;
    }edge[maxm<<1];
    int n,m,cnt,ans;
    void add(int from,int to){
    	edge[++cnt].to=to;
    	edge[cnt].next=head[from];
    	head[from]=cnt;
    }
    bool find(int u){
    	for(int i=head[u];i;i=edge[i].next){
    		int v=edge[i].to;
    		if(!vis[v]){
    			vis[v]=1;
    			if(!match[v]||find(match[v])){
    				match[v]=u;
    				return 1;
    			}
    		}
    	}
    	return 0;
    }
    void Solve(){
    	scanf("%d%d",&n,&m);
    	for(int i=1,u,v;i<=m;++i){
    		scanf("%d%d",&u,&v);
    		add(u,v+n);
    		add(v+n,u);
    	}
    	for(int i=1;i<=(n<<1);++i){
    		memset(vis,0,sizeof(vis));
    		if(find(i)) ans++;
    	}
    	printf("%d",ans/2);
    }
    int main(){
    	Solve();
    	return 0;
    }
    
    

    然后是这个:

    这个题稍微有点难度,第一次做的时候想把节点设置成猫和狗,然后就挂了。
    事实上每只猫和狗能够满足多个人的需要,并不满足”1要素“。
    而且注意到每个人都只会被一对猫和狗满足。
    因此考虑把节点设置成人,把喜欢猫(狗)a的与不喜欢猫(狗)a的人进行连边。

    概念:二分图的独立集:任意两点之间都没有边相连的点集。

    定理:设G是有n个节点的二分图,G的最大独立集的大小等于n减去最大匹配数。

    然后求最大独立集即可。
    代码:

    #include <bits/stdc++.h>
    using namespace std;
    const int maxm=10000+10;
    struct node{
    	int next,to;
    }edge[maxm<<1];
    int head[maxm<<1],vis[maxm<<1],match[maxm<<1];
    int cnt,n,m,k,ans;
    char s1[maxm<<1][10],s2[maxm<<1][10];
    void add(int from,int to){
    	edge[++cnt].to=to;
    	edge[cnt].next=head[from];
    	head[from]=cnt;
    }
    bool find(int u){
    	for(int i=head[u];i;i=edge[i].next){
    		int v=edge[i].to;
    		if(!vis[v]){
    			vis[v]=1;
    			if(!match[v]||find(match[v])){
    				match[v]=u;
    				return true;
    			}
    		}
    	}
    	return false;
    }
    void Solve(){
    	scanf("%d%d%d",&n,&m,&k);
    	for(int i=1;i<=k;++i) scanf("%s%s",s1[i],s2[i]);
    	for(int i=1;i<=k;++i){
    		for(int j=1;j<=k;++j){
    		    if(i==j) continue;
    			if(strcmp(s1[i],s2[j])==0){
    				add(i,j);
    				add(j,i);
    			}
    		}
    	}
    	for(int i=1;i<=k;++i){
    		memset(vis,0,sizeof(vis));
    		if(find(i)) ans++;
    	}
    	printf("%d",k-ans/2);
    }
    int main(){
    	Solve();
    	return 0;
    }
    
    
  • 相关阅读:
    特殊字符大全
    ASP執行文件下載
    在 Access 里使用查询建立 存储过程/视图, 并使用 ASP 执行
    通過windows的排定工作來執行存儲過程
    前台页面中用js取得eWebEditor的值
    IE7外觀優化
    电话订票
    在BIOS设置“扩展或板载”显卡的方法
    子窗口中操作父窗口对像(javascript)(转)
    mailto用法
  • 原文地址:https://www.cnblogs.com/wwcdcpyscc/p/13775315.html
Copyright © 2011-2022 走看看