我们现在讲下二分图匹配
1.什么是二分图
二分图又称作二部图,是图论中的一种特殊模型。 设(G=(V,E))是一个无向图,如果顶点(V)可分割为两个互不相交的子集((A,B)),并且图中的每条边((i,j))所关联的两个顶点i和j分别属于这两个不同的顶点集((iin A,jin B)),则称图(G)为一个二分图。
2.二分图的问题
我们来想一个比较浅显的问题,我们有(n)个男孩,(m)个女孩。每个男孩只愿意和一些他指定的女孩在一起,每个女孩也只希望和她指定的一些男孩在一起。当然同性肯定不能在一起(不就是lily和gay吗)当然肯定需要双方都同意才能在一起啊【手动滑稽】,此时我们现在想知道最多有多少对能成?
3.最佳二分图匹配(dinic)
我们可以用一个网络流算法来解这个问题
这里我们只需要建一个这样图,样例如下
输入格式:
第1行:n,m
第2~n+1行,第i行先是一个(B_i),然后是(B_i)个数,第j个数(x_j)表示第i个男生愿意和第(x_j)个女生在一起
第n+2~n+m+2行,第i行先是一个(G_i),然后是(G_i)个数,第j个数(y_j)表示第i个男生愿意和第(y_j)个女生在一起
输出格式:
一个整数表示最多的配对对数
样例输入:
3 3
1 1
2 1 2
3 1 2 3
2 1 2
2 1 3
1 3
样例输出:
2
现在我们就可以跑最大流了,最大流就是我们需要的答案
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define Maxn 1000001
#define INF 0x7fffffff
using namespace std;
bool vis[1001][1001];
class Dinic{
private:
int n,s,t,w[Maxn],v[Maxn],next[Maxn],head[Maxn],cnt;
int depth[Maxn];
void add_edge(int x,int y,int z){
next[++cnt]=head[x];
v[cnt]=y;
w[cnt]=z;
head[x]=cnt;
return;
}
public:
void init(int x,int y,int z){
n=x,s=y,t=z;
cnt=-1;
memset(head,-1,sizeof(head));
memset(next,-1,sizeof(next));
return;
}
void addedge(int x,int y,int z){
add_edge(x,y,z);
add_edge(y,x,0);
return;
}
bool bfs(){
queue<int>q;
memset(depth,0,sizeof(depth));
depth[s]=1;
q.push(s);
do{
int u=q.front();
q.pop();
for(int i=head[u];~i;i=next[i]){
if(w[i]>0 and !depth[v[i]]){
depth[v[i]]=depth[u]+1;
q.push(v[i]);
}
}
}while(!q.empty());
return depth[t];
}
int dfs(int u,int now){
if(u==t)
return now;
for(int i=head[u];~i;i=next[i]){
if(w[i]>0 and depth[v[i]]==depth[u]+1){
int d=dfs(v[i],min(now,w[i]));
if(d>0){
w[i]-=d;w[i^1]+=d;
return d;
}
}
}
return 0;
}
int dinic(){
int ans=0;
while(bfs()){
ans+=dfs(s,INF);
}
return ans;
}
}dinic;
int n,m,s,t;
int main(){
scanf("%d%d",&n,&m);
dinic.init(n,1,n+m+2);
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
for(int j=1;j<=x;j++){
int y;
scanf("%d",&y);
vis[i][y]=1;
}
dinic.addedge(1,i+1,1);
}
for(int i=1;i<=m;i++){
int x;
scanf("%d",&x);
for(int j=1;j<=x;j++){
int y;
scanf("%d",&y);
if(vis[y][i]){
dinic.addedge(y+1,n+i+1,1);
}
}
dinic.addedge(n+i+1,n+m+2,1);
}
printf("%d
",dinic.dinic());
return 0;
}
4.我们再来看看匈牙利
匈牙利算法(Hungarian method)是由匈牙利数学家Edmonds于1965年提出,因而得名。匈牙利算法是基于Hall定理中充分性证明的思想,它是二分图匹配最常见的算法,该算法的核心就是寻找增广路径,它是一种用增广路径求二分图最大匹配的算法。
复杂度
时间复杂度邻接矩阵:最坏为(O(n^3)) 邻接表:(O(nm))
空间复杂度 邻接矩阵: (O(n^2)) 邻接表: (O(n+m))
思路
这就是匈牙利算法的流程,其中找妹子是个递归的过程,最最关键的字就是“腾”字
其原则大概是:有机会上,没机会创造机会也要上
代码
这是luogu模板P3386
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
bool f[1001][1001],vis[1001];
int n,m,e,ans,mat[1001];
bool dfs(int x){
for(int i=1;i<=m;i++){
if(f[x][i] and !vis[i]){
vis[i]=1;
if(!mat[i]){
mat[i]=x;
return 1;
}else{
if(dfs(mat[i])){
mat[i]=x;
return 1;
}
}
}
}
return 0;
}
int main(){
scanf("%d%d%d",&n,&m,&e);
for(int i=1;i<=e;i++){
int x,y;
scanf("%d%d",&x,&y);
if(x<=n and y<=m)f[x][y]=1;
}
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
if(dfs(i))ans++;
}
printf("%d
",ans);
return 0;
}