二分图
二分图
二分图,又称二部图,英文名叫 Bipartite graph。二分图是节点由两个集合组成,且两个集合内部没有边的图。
性质
如果两个集合中的点分别染成黑色和白色,可以发现二分图中的每一条边都一定是连接一个黑色点和一个白色点。
判定
如何判定一个图是不是二分图呢?
可以使用 DFS 或者 BFS 来遍历这张图。如果发现了奇环,那么就不是二分图,否则是。
二分图最大匹配
二分图最大匹配:给定一个二分图 G,即分左右两部分,各部分之间的点没有边连接,要求选出一些边,使得这些边没有公共顶点,且边的数量最大。
增广路算法 Augmenting Path Algorithm
因为增广路长度为奇数,路径起始点非左即右,所以我们先考虑从左边的未匹配点找增广路。
注意到因为交错路的关系,增广路上的第奇数条边都是非匹配边,第偶数条边都是匹配边,于是左到右都是非匹配边,右到左都是匹配边。
于是我们给二分图 定向,问题转换成,有向图中从给定起点找一条简单路径走到某个未匹配点,此问题等价给定起始点 s 能否走到终点 t。
那么只要从起始点开始 DFS 遍历直到找到某个未匹配点,O(m)。
未找到增广路时,我们拓展的路也称为 交错树。
因为要枚举 n 个点,总复杂度为 O(nm)。
code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;
cs int N=5e4+5;
int h[1005];
int had_cp[1005],cp[1005];
namespace edge{
struct qwq{
int v,nxt;
}e[N];
il void add(int u,int v,int id){
e[id]={v,h[u]},h[u]=id;
}
} using namespace edge;
namespace Bipartite_graph{
bool have_cp(int u,int tim){
for(ri int i=h[u];i;i=e[i].nxt){
if(had_cp[e[i].v]!=tim){
had_cp[e[i].v]=tim;
if(!cp[e[i].v]||have_cp(cp[e[i].v],tim)){
cp[e[i].v]=u;
return 1;
}
}
}
return 0;
}
} using namespace Bipartite_graph;
int n,m,ed,as;
int main(){
cin>>n>>m>>ed;
for(ri int i=1,u,v;i<=ed;++i){
cin>>u>>v;add(u,v,i);
}
for(ri int i=1;i<=n;++i){
as+=have_cp(i,i);
}
cout<<as;
return 0;
}
最大流 Dinic
所以我们将源点S设在左半部分左边,向左边所有的点连一条容量为1的边。汇点T设在右半部分的右边。从所有的右半部分的点连向汇点一条容量为1的边,暴力跑一边dinic就好啦~
答案就是左边通过已有的边流到右边的流量最大值
code
#include<bits/stdc++.h>
#define il inline
#define cs const
#define ri register
using namespace std;
namespace Q{
il int rd(){
ri int x=0;ri bool f=0;ri char c=getchar();
while(!isdigit(c)) f|=(c==45),c=getchar();
while(isdigit(c)) x=x*10+(c^48),c=getchar();
return f?-x:x;
}
il void wt(int x){
if(x<0) x=-x,putchar(45);
if(x>=10) wt(x/10);
return putchar(x%10|48),void();
}
} using namespace Q;
cs int N=1005,M=51005,inf=0x3f3f3f3f;
int n,m,o,s,t;
namespace edge{
int h[N],cnt=1;
struct qwq{
int v,w,nxt;
}e[M<<1];
il void add(int u,int v){
e[++cnt]={v,1,h[u]},h[u]=cnt;
e[++cnt]={u,0,h[v]},h[v]=cnt;
return;
}
} using namespace edge;
namespace Dinic{
int dis[N],now[N];
queue<int> q;
il bool bfs(){
memset(dis,0,sizeof(dis));
dis[s]=1,q.push(s);
ri int u;
while(!q.empty()){
u=q.front(),q.pop(),now[u]=h[u];
for(ri int i=h[u];i;i=e[i].nxt){
if(!dis[e[i].v]&&e[i].w){
dis[e[i].v]=dis[u]+1;
q.push(e[i].v);
}
}
}
return dis[t];
}
il int dfs(int u,int res){
if(u==t) return res;
ri int tot=res,ls;
for(ri int i=h[u];i;i=e[i].nxt){
now[u]=i;
if(dis[e[i].v]==dis[u]+1&&e[i].w){
ls=dfs(e[i].v,min(tot,e[i].w));
e[i].w-=ls,e[i^1].w+=ls,tot-=ls;
if(!tot) break;
}
}
return res-tot;
}
il int dinic(){
ri int as=0;
while(bfs()) {
as+=dfs(s,inf);
}
return as;
}
} using namespace Dinic;
signed main(){
n=rd(),m=rd(),o=rd();
s=n+m+1,t=m+n+2;
for(ri int i=1,u,v;i<=o;++i){
u=rd(),v=rd(),add(u,v+n);
}
for(ri int i=1;i<=n;++i) add(s,i);
for(ri int i=1;i<=m;++i) add(n+i,t);
wt(dinic());
return 0;
}
I went to the woods because I wanted to live deliberately, I wanted to live deep and suck out all the marrow of life, and not when I had come to die, discover that I had not live.