二分图学习笔记

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

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

蓝书原话:

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

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;
}

posted @ 2020-10-07 07:02  “起个名字真难♘”  阅读(207)  评论(0编辑  收藏  举报