preparing

二分图

二分图基础知识

定义

二分图又称作二部图,是图论中的一种特殊模型。 设\(G\)=(\(V\),\(E\))是一个无向图,如果顶点\(V\)可分割为两个互不相交的子集(\(A\),\(B\)),并且图中的每条边(\(i\)\(j\))所关联的两个顶点\(i\)\(j\)分别属于这两个不同的顶点集(\(i\in A\),\(j\in B\)),则称图\(G\)为一个二分图。


通俗地讲,二分图其实就是一幅特殊的图,满足可以把点分成两部分,使得所有的边都连接两部分,即同一部分中的点不相连。如图,每一条边都连接子集\(x\)(红色的点)和\(y\)(蓝色的点),因此此图是二分图。

性质

二分图\(G\)的充要条件[1]是:

  1. G至少有两个顶点;
  2. 所有环的长度均为偶数。

第一条显然成立,而第二条不难理解:如果二分图中有一个环,那么假设起点从点集\(A\)出发,那么,经过奇数条边后的点一定在点集\(B\)中,偶数条边后的点一定在点集\(A\)中(逆定理亦成立)。又由于环的结尾就是起点,所以结尾也一定在点集\(A\)中,所以边数一定为偶数。如上图点\(x1,y2,x3,y3,x1\)构成的环就有\(4\)条边。

判定

根据定义和性质,我们可以得出二分图的其中两种判定方法:

  • 根据定义,我们可以从任意一点开始图染色。首先将该点染成一种颜色(以下称“第一种颜色”),将与之相连的点染成另一种颜色(以下称“第二种颜色”),再将与这些点相连的点染回第一种颜色……以此类推,交替染两种颜色。若出现将要染色的某点原来有颜色且与要染的颜色不同,显然此图不是二分图。如果所有点全部被染色,那么此图为二分图,且两种颜色的点就是两个点集。
  • 根据性质,我们可以用\(DFS\)\(BFS\)遍历这张图。如果发现了奇环,则不是二分图,否则是二分图。

匹配

定义

二分图\(G\)匹配指一组没有公共端点的不是圈的边构成的集合。

也就是说,二分图的匹配就是一幅二分图的一个边集,使得任意两条边都没有共同的顶点。如上图图1就是该图的匹配之一。
如果一个匹配中所有点都与某条边相连,则称此匹配为完美匹配,可以证明其最多有\(n!\)个(\(n\)为点集点的个数)。
如果一个匹配中不能再加入任何一条边来增加匹配的边数,则称其为极大匹配
所有极大匹配中总边数最大的成为该二分图的最大匹配。如上图图2为该图的最大匹配(不唯一)。
可见:完美匹配一定是最大匹配,而最大匹配不一定是完美匹配

匈牙利算法求最大匹配

增广路

若二分图\(G\)的一个匹配为\(M\),有一条路径的边交替出现在\(M\)中,即依次不在\(M\)中、在\(M\)中、不在\(M\)中……这样的路径称为一条交错路径。特殊地,若一条交错路径的首尾两条边均\(M\)中,则这条路被称为增广路径

如图,左边是图的一个匹配,而右图为其中一条增广路。这条增广路满足“边交替出现在\(M\)中",具体地,(\(x_1\),\(y_3\))(\(x_4\),\(y_4\))不在匹配中而(\(y_3\),\(x_4\))在匹配中,那么,如果将这条路径取反,即变成(\(x_1\),\(y_3\))(\(x_4\),\(y_4\))在匹配中但(\(y_3\),\(x_4\))不在匹配中会怎样呢?

匈牙利算法

对于刚刚的问题:若存在一条增广路,既然其开始与结尾都是不在匹配中,且边交替出现在匹配中,那么就是说,不在匹配中的边一定比在匹配中的边多一。那么,我们将该路径取反,在匹配中的边一定就比不在匹配中的边多一了,即匹配的边数就会增加一条。因此,我们可以通过不断寻找增广路并将其取反来不断增加匹配中边的数量。等到找不到增广路了,也就说明匹配的边数已经达到最大,即为最大匹配。

匈牙利算法的具体步骤如下(以图\(1\)为例):
如图\(1\)二分图,两点集为\(x\)\(y\),都有\(4\)个点。

  1. 首先,搜索到\(x_1\),搜所有与它相连的边,第一条为\((x_1,y_1)\),发现\(y_1\)并没有被匹配过,于是将\(x_1\)\(y_1\)相连。
  2. 继续搜到\(x_2\),搜边,第一条为\((x_2,y_3)\)\(y_3\)刚好没有匹配过,连接\(x_2\)\(y_3\)(如图\(2\))。
  3. 继续搜到了\(x_3\),搜边,第一条为\((x_3,y_1)\)。但是,\(y_1\)已经被\(x_1\)匹配了,于是我们让\(x_1\)去找有没有其它点可以匹配。于是找到了\(y_3\),但是\(y_3\)又被\(x_2\)匹配了,同样地,搜\(x_2\)有没有其它点,搜到了\(y_4\),于是\(x_2\)\(y_4\)匹配,\(x_1\)就能与\(y_3\)匹配,\(x_3\)就能与\(y_1\)匹配了(如图\(4\))。
  4. 继续搜到了\(x_4\),第一条边为\((x_4,y_1)\)\(y_1\)\(x_3\)匹配了,于是\(x_3\)去找有没有其他人可以匹配,可惜没找到,所以\(x_3\)不同意\(x_4\)抢走\(y_1\)\(x_4\)只能搜下一条边\((x_4,y_2)\)\(y_2\)没人匹配,故\(x_4\)\(y_2\)匹配。
  5. 所有点搜完,输出最大匹配数\(4\)

综上,匈牙利算法的主要思想就是:不断搜索每个点,搜索与点相邻的边,若边的另一端未被匹配,则与这个点匹配;若已被匹配,让和它匹配的点找其他点匹配,若找不到,则找下一条边,否则位置被空出来,这个点就可以与之匹配。
那么匈牙利算法为什么可以找到最大匹配呢?我们以上图图\(4\)这一步为例。这一步中,\(x_3\)找到了\(y_1\),但是\(y_1\)已经被\(x_1\)匹配了,于是,\(x_3\)\(y_1\)预定了,让\(x_1\)去找其它匹配。\(x_1\)找到了\(y_3\),可是\(y_3\)已经被\(x_2\)匹配。于是,\(x_1\)\(x_3\)一样先把\(y_3\)预定了,让\(x_2\)去找其它点。而\(x_2\)找到了\(y_4\),于是刚才预定位置的两个点就可以匹配了。让我们拿出所有涉及到的边,如图蓝色部分所示,图\(1\)为该步骤进行前,图\(2\)为进行后。

由图可知,蓝色路径刚好是一条增广路,进行后刚好是取反了这条增广路,所以这就是为什么匈牙利算法可以找到最大匹配。

  • 代码

代码如下,有注释。
例题:P3386 【模板】二分图最大匹配

#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 505
using namespace std;
int n,m,e,u,v;
int g[maxn][maxn];//存边,g[i][j]=1表示i点和j点有单向边
int ans=0;
int book[maxn],use[maxn];
bool search(int x){
	for(int i=1;i<=m;i++){//搜索所有另一个点集的点
		if(g[x][i]&&!book[i]){//若有边且未被预订
			book[i]=1;//将这个店预订了
			if(!use[i]||search(use[i])){//若未被用过或与它匹配的点可以给它腾出位置则与这个点匹配
				use[i]=x;
				return 1;//返回1
			}
		}
	}
	return 0;//无法匹配返回0
}
int main(){
	scanf("%d%d%d",&n,&m,&e);
	for(int i=1;i<=e;i++){
		scanf("%d%d",&u,&v);
		g[u][v]=1;
	}
	for(int i=1;i<=n;i++){//不断搜索每个点
		memset(book,0,sizeof(book));//一定要清空
		if(search(i)){//若搜到
			ans++;//答案加1
		}
	}
	printf("%d",ans);
	return 0;
}

最大流求最大匹配

最大流

最大流求最大匹配

最大流和二分图看起来不是毫不相干吗?怎么能用最大流求二分图最大匹配呢?
网络流一定要有一个源点和一个汇点,那我们在二分图中建源点和汇点不就好了吗?如图\(1\)的二分图,我们建立源点连接点集\(x\)的所有点,建立汇点连接点集\(y\)的所有点,且边权全部为\(1\)。这时跑最大流得到的答案即为最大匹配。

  • \(Q\):为什么这样可以求出最大匹配呢?
    \(A\):我们把有水流过看作是选择这条边,没有水流过看作是不选这条边,因为每条边的边权全都是\(1\),所以流过一条边就有\(1\)的流量,得到的答案就是有多少条边被选择。又因为求的是最大流,所以满足有最多水管被选择,所以得到的就是最大匹配。
  • \(Q\):网络流的每个节点都可以有水往不同水管流,但是二分图的最大匹配每个节点只能有一条边相连,为什么得到的答案还是正确的呢?
    \(A\):因为每条边的容量都是\(1\),所以一旦有水流过这条边就满流了,以后的水也不会往这里流了,所以得到的答案是正确的。
  • \(Q\):代码?
    \(A\)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define maxn 550
#define maxm 5505
#define ll long long
#define inf 0x3fffffff
using namespace std;
int n,m,e,s,t,u,v;
int head[maxn],tt=1;
struct node{
	int to,dis,nex;
}a[maxm*2];
void add(int from,int to,int dis){
	a[++tt].to=to;
	a[tt].dis=dis;
	a[tt].nex=head[from];
	head[from]=tt;
}
bool vis[maxn];
int dep[maxn],cur[maxn];
bool bfs(){
	for(int i=0;i<=n+m+2;i++){
		vis[i]=0;
		dep[i]=inf;
		cur[i]=head[i];
	}
	queue<int> q;
	vis[s]=1;
	q.push(s);
	dep[s]=0;
	while(!q.empty()){
		int top=q.front();
		q.pop();
		for(int i=head[top];i;i=a[i].nex){
			if(dep[top]+1<dep[a[i].to]&&a[i].dis){
				dep[a[i].to]=dep[top]+1;
				if(!vis[a[i].to]){
					vis[a[i].to]=1;
					q.push(a[i].to);
				}
			}
		}
	}
	if(dep[t]==dep[0]){
		return 0;
	}
	return 1;
}
ll ans=0;
int dfs(int x,int minn){
	if(x==t){
		ans+=minn;
		return minn;
	}
	int use=0;
	for(int i=cur[x];i;i=a[i].nex){
		cur[x]=i;
		if(dep[a[i].to]==dep[x]+1&&a[i].dis){
			int search=dfs(a[i].to,min(minn-use,a[i].dis));
			if(search>0){
				use+=search;
				a[i].dis-=search;
				a[i^1].dis+=search;
				if(use==minn){
					break;
				}
			}
		}
	}
	return use;
}
void dinic(){
	while(bfs()){
		dfs(s,inf);
	}
	printf("%lld",ans);
}
int main(){
	scanf("%d%d%d",&n,&m,&e);
	for(int i=1;i<=e;i++){
		scanf("%d%d",&u,&v);
		v+=n;
		add(u,v,1);
		add(v,u,0);
	}
	s=n+m+1;
	t=n+m+2;
	for(int i=1;i<=n;i++){
		add(s,i,1);
		add(i,s,0);
	}
	for(int i=1;i<=m;i++){
		add(i+n,t,1);
		add(t,i+n,0);
	}
	dinic();
	return 0;
}

最小点覆盖

定义

假如选了一个点就相当于覆盖了以它为端点的所有边,最小点覆盖即为选择最少的点来覆盖所有的边。

方法

对于一个二分图,选出若干点使得所有边都与这些点中的一个相连,点数最少的即为最小点覆盖。
最小点覆盖等于最大匹配。
求最小点覆盖的方法如下:

从右边没有匹配的点出发找所有不完整的增广路,把所有经过的点标记。右边没有标记的点和左边标记的点的集合即为最小点覆盖。

建议看这篇

  1. 充要条件,即充分必要条件。若\(A\)可推出\(B\)\(B\)也可推出\(A\),则\(A\)\(B\)的充要条件。 ↩︎

posted @ 2021-08-20 15:54  qzhwlzy  阅读(470)  评论(0编辑  收藏  举报