网络流总结(二)二分图

相关概念:

二分图:一个图被分成两部分,相同的部分没有边

匹配:两两不含公共端点的边的集合M称为匹配

最大匹配:元素最多的M则称为最大匹配

完美匹配:最大匹配数M满足:2*M=V

对于二分图的最大匹配问题,可以加上一个s,一个t,转为求最大s-t流

 

二分图的最小顶点覆盖 最大独立集 最大团

(这一部分转自https://www.cnblogs.com/jianglangcaijin/p/6035945.html

二分图的最小顶点覆盖

定义:假如选了一个点就相当于覆盖了以它为端点的所有边。最小顶点覆盖就是选择最少的点(左右两边都可选)来覆盖所有的边。

方法:最小顶点覆盖等于二分图的最大匹配。

我们用二分图来构造最小顶点覆盖。

对于上面这个二分图,顶点分为左右两个集合,X集合包含1,2,3,4,Y集合包含5,6,7,8,9.假如现在我们已经找到一个最大匹配M,就是上面的红线所标注的M={(1,7),(2,5),(4,8)}。我们作如下定义:(1)定义1、2、4、5、7、8为已经匹配过的点,其他点为未匹配的点;(2)定义(4,8)、(1,7)、(2,5)为已匹配的边,其他边为未匹配的边。

下面我们从Y集合中找出未匹配的点,就是上面标记的6和9。每次我们从右边选出一个未匹配的点,从该点出发, 做一条未匹配边->匹配边->未匹配边->……->匹配边(注意最后以匹配边结尾),并且标记用到的点,得到下图:

其中紫色的边为我们刚才画的边,其中标记的点有2、4、5、6、8、9。 上图的两条路为:(1)9->4->8->2->5 (2)6->2->5。这两条路都是未匹配边->匹配边->未匹配边->……->匹配边。(注意如果一个右侧未匹配点有多条边,那么这样的从该点开始的路径就有多条,上面的6和9都只有一条边,所以从每个点开始的这样的路径只有一条)。

现在我们将左侧标记的点2、4和右侧未标记的点7选出组成集合S, 那个S就是一个最小顶点覆盖集,就是S集合可以覆盖所有的边。下面证明:

(1)|S|=M,即最小顶点覆盖等于二分图最大匹配:左边标记的点全都为匹配边的顶点,因为我们构造路径的时候左边的点向右找的边都是最大匹配的边;右边未标记的点也为二分图最大匹配边的顶点。而且左边标记的加上有边未标记的正好是最大匹配的数目。

(2)S能覆盖所有的边。所有的边可以分为下面三种情况:a、左端点标记、右端点标记;这些边一定被左侧标记的点覆盖,比如上面的2,4;b、右端点未标记;这些边一定被右侧未标记的点覆盖,比如上面的7;c、左端点未标记、右端点标记。

下面我们证明c这种边压根就不会存在:若c是最大匹配中的边,由于右端点不可能是一条路径的起点(因为我们的起点都是从Y集合中未匹配的点开始的),于是右端点的标记只能是在构造中从左边连过来,这是左端点必定被标记了,这时c就转化成了a;若c属于未匹配边,那么左端点必定是一个匹配点,那么c的右端点必定是一条路径的起始点,因此c的左端点也会成为一条路径的第二个点而被标记,这时c也就成了a。所以c这种边肯定是不存在的。

(3)S是最小的顶点集:因为最大匹配为M,而|S|=M,所以如果S中的点再少,那么连M个匹配的边都不能覆盖。

 

二分图的最大独立集

定义:选出一些顶点使得这些顶点两两不相邻,则这些点构成的集合称为独立集。找出一个包含顶点数最多的独立集称为最大独立集。

方法:最大独立集=所有顶点数-最小顶点覆盖

 

在上面这个图中最小顶点覆盖=3,即2,4,7构成最小顶点覆盖,则其他点6个构成最大独立集。且其他点不可能相连。假设其他点相连则这条边必定没有被2,4,7 覆盖,与2,4,7是最小顶点覆盖矛盾。因此其他点之间必定没有边。而2,4,7是最小顶点覆盖,所谓最小就是不能再小了,因此我们的独立集就是最大了。

二分图的最大团

定义:对于一般图来说,团是一个顶点集合,且由该顶点集合诱导的子图是一个完全图,简单说,就是选出一些顶点,这些顶点两两之间都有边。最大团就是使得选出的这个顶点集合最大。对于二分图来说,我们默认为左边的所有点之间都有边,右边的所有顶点之间都有边。那么,实际上,我们是要在左边找到一个顶点子集X,在右边找到一个顶点子集Y,使得X中每个顶点和Y中每个顶点之间都有边。

方法:二分图的最大团=补图的最大独立集。

补图的定义是:对于二分图中左边一点x和右边一点y,若x和y之间有边,那么在补图中没有,否则有。

这个方法很好理解,因为最大独立集是两两不相邻,所以最大独立集的补图两两相邻。

二分图求最大匹配模板:

poj 3041 - Asteroid

思路:

把光束当成顶点,行星当成连接对应光束的边,这样转换后,求的是二分图最小顶点覆盖问题,也就是最大匹配问题。

代码一:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue> 
 
using namespace std;

struct Edge{
	int from,to,cap,flow;
	Edge(int u,int v,int c,int f):from(u),to(v),cap(c),flow(f){}
};
const int INF=99999999,maxn=1100;
int n,k,s,t;
vector<Edge> edges;//存正边和反向边,边数的两倍 
vector<int> G[maxn];//邻接表,G[i][j]表示节点i的第j条边在edges数组中的序号 
int a[maxn];//a[i]表示源点s到节点i的路径上的最小残留量 
int p[maxn];//p[i]记录i的前驱,是用在edges数组里的序号表示的 

/*void init(int n){
	for(int i=0;i<n;i++){
		G[i].clear();
	}
    edges.clear();
}*/

void AddEdge(int from,int to,int cap){
	edges.push_back(Edge(from,to,cap,0));
	edges.push_back(Edge(to,from,0,0));//反向弧 
	int m=edges.size();
	G[from].push_back(m-2);
	G[to].push_back(m-1);
}

int Maxflow(int s,int t){
	int flow=0;
	for(;;){
		memset(a,0,sizeof(a));
		queue<int> q;
		q.push(s);
		a[s]=INF;
		while(!q.empty()){
			int x=q.front();q.pop();
			
			for(int i=0;i<G[x].size();i++){//x点对应的所有正向弧和反向弧 
				Edge& e=edges[G[x][i]];
				if(!a[e.to]&&e.cap>e.flow){
					p[e.to]=G[x][i];
					a[e.to]=min(a[x],e.cap-e.flow);
					q.push(e.to);
				}
			}
			
			if(a[t])break;
		}
		if(!a[t])break;//如果找不到增广路,则当前流已经是最大流
		for(int u=t;u!=s;u=edges[p[u]].from){
			edges[p[u]].flow+=a[t];//更新正向流量 
			edges[p[u]^1].flow-=a[t];//更新反向流量 
		} 
		flow+=a[t];//流加上 
	} 
	return flow;
} 

int main(){
	int r,c;
	scanf("%d%d",&n,&k);
	s=0,t=2*n+1;
	for(int i=1;i<=n;i++){
	    AddEdge(s,i,1);	
	}
	for(int i=n+1;i<=2*n;i++){
	    AddEdge(i,t,1);	
	}
	for(int i=0;i<k;i++){
		scanf("%d%d",&r,&c);
		AddEdge(r,n+c,1);
	}
	printf("%d\n",Maxflow(s,t));
}

代码二:

利用所有边的容量都是1以及二分图的性质,我们还可像下面这样将二分图的最大匹配算法更简单地实现

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue> 
 
using namespace std;

const int INF=99999999,maxv=1100;
int V;
vector<int> G[maxv];
int match[maxv];
bool used[maxv];
//向图中增加一条连接u、v的边 
void add_edge(int u,int v){
	G[u].push_back(v);
	G[v].push_back(u);
}
//dfs找增广路 
bool dfs(int v){
	used[v]=true;
	for(int i=0;i<G[v].size();i++){
		int u=G[v][i],w=match[u];
		if(w<0||!used[w]&&dfs(w)){
			match[v]=u;
			match[u]=v;
			return true;
		}
	}
	return false;
}
//求解二分图的最大匹配 
int bipartite_matching(){
	int res=0;
	memset(match,-1,sizeof(match));
	for(int v=0;v<V;v++){
		if(match[v]<0){
			memset(used,0,sizeof(used));
			if(dfs(v)){
				res++;
			}
		}
	}
	return res;
}
int main(){
	int n,k,r,c;
	scanf("%d%d",&n,&k);
	V=n*2;
	for(int i=0;i<k;i++){
		scanf("%d%d",&r,&c);
		add_edge(r-1,n+c-1);
	}
	printf("%d\n",bipartite_matching());
}

 

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值