相关概念:
二分图:一个图被分成两部分,相同的部分没有边
匹配:两两不含公共端点的边的集合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());
}