概念
对于一个图(G=(V,E)),若能将其点集分为两个互不相交的两个子集(X、Y),
使得(X∩Y=phi),且对于(G)的边集(V),若其所有边的顶点全部一侧属于(X),
一侧属于(Y),则称图(G)为一个二分图。
定理
当且仅当无向图(G)的回路个数为偶数时,图(G)为一个二分图。
无回路的图也是二分图。
判定
在二分图(G)中,任选一个点(V),
使用(BFS)预处理出其他点相对于(V)的距离(边权为(1))
对于每一条边(E),枚举它的两个端点,若其两个端点的值,
一个为奇数,一个为偶数,则图(G)为一个二分图。
匹配
对于一个二分图(G)的子图(M)(前提是在二分图中),若(M)的边集(E)的的任意两条边都不连接同一个顶点,
则称(M)为(G)的一个匹配。特别的,其中边数最多的(M)称为二分图的最大匹配。
匈牙利算法
一、算法描述
建立有向图(G),分为二分图的左侧和右侧。
优先选择左侧序号更小的连接可能的边。
对于两个点的目标点“冲突”的时候,采取“协商”的办法。
即序号小的连接可能连接的另一条边。
若“协商”失败,则放弃序号较大的点的边。
二、步骤
推荐看这篇大佬的博客千古神犇zay 以上内容基本来自这里。
三、总结
(1))如果后来的和以前的发生矛盾,则以前的优先退让。
(2))如果以前的退让之后自己无法匹配,则以前的拒绝退让,新来的去寻找下一个匹配。
(3))如果新来的谁也匹配不上了,那就这么单着吧。
所以说做法已经很清晰了,直接上代码吧。
(Code)
//二分图匹配-匈牙利算法 1000ms
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define maxn 2005
#define re register
using namespace std;
int n,m,es,x,y,cnt,head[maxn];
inline int read()
{
int x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int ans,used[maxn];
bool vis[maxn];
struct Edge{
int v,nxt;
}e[maxn*maxn];
inline void add(int u,int v)
{
e[++cnt].v=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
bool dfs(int x)
{
for(int i=head[x];i;i=e[i].nxt)
{
int ev=e[i].v;
if(!vis[ev])
{
vis[ev]=true;
if(used[ev]==0||dfs(used[ev]))//要注意这里dfs的是used[ev]而不是ev
{
used[ev]=x;
return true;
}
}
}
return false;//注意这里返回false,否则会TLE
}
void Maxmatch()
{
for(re int i=1;i<=n;++i)
{
memset(vis,false,sizeof(vis));
ans+=dfs(i);
}
}
int main()
{
n=read(),m=read(),es=read();
for(re int i=1;i<=es;++i)
{
x=read(),y=read();
if(x>n||y>m) continue;//这是题目的锅
add(x,y);
}
Maxmatch();
printf("%d
",ans);
return 0;
}
最大流(Dinic)做法
//网络最大流Dinic做法
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cstring>
#define maxn 20005
#define maxm 2000010
using namespace std;
const int INF =999999999;
queue<int> q;
int kx[maxm],ky[maxm],cnt1;
int n,m,x,y,z,es,maxflow,deep[maxn];
int s,t;
struct Edge{
int v,nxt=-1,w;
}e[maxm<<2];
int cnt=-1,head[maxn];
void add(int x,int y,int z)
{
e[++cnt].v=y;
e[cnt].w=z;
e[cnt].nxt=head[x];
head[x]=cnt;
}
int BFS()
{
memset(deep,0,sizeof(deep));//deth记录深度
while(!q.empty())
q.pop();
deep[s]=1;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i!=-1;i=e[i].nxt)
{
if(e[i].w>0&&deep[e[i].v]==0)//分层
{
deep[e[i].v]=deep[u]+1;
q.push(e[i].v);
}
}
}
if(deep[t]!=0)//当汇点的深度不存在时,说明不存在分层图,同时也说明不存在增广路
return 1;
else
return 0;
}
int dfs(int now,int limit)
{
if(now==t) return limit;
for(int i=head[now];i!=-1;i=e[i].nxt)
{
//cur[now]=i;
if((deep[e[i].v]==deep[now]+1)&&(e[i].w!=0))
{
int di=dfs(e[i].v,min(limit,e[i].w));
if(di>0)
{
e[i].w-=di;
e[i^1].w+=di;
return di;
}
}
}
return 0;
}
int Dinic()
{
int ans=0;
while(BFS())
while(int tmp=dfs(s,INF))
ans+=tmp;
return ans;
}
int main()
{
// freopen("1.in","r",stdin);
memset(head,-1,sizeof(head));
scanf("%d%d%d",&n,&m,&es);
s=0,t=n+m+1;
for(register int i=1;i<=n;++i)
{
add(s,i,1);
add(i,s,0);
}
for(register int i=1;i<=m;++i)
{
add(i+n,t,1);
add(t,i+n,0);
}
for(register int i=1;i<=es;++i)
{
scanf("%d%d",&x,&y);
if(x>n||y>m) continue;
kx[++cnt1]=x;
ky[cnt1]=y;
}
for(register int i=1;i<=cnt1;++i)
{
add(kx[i],ky[i]+n,1);
add(ky[i]+n,kx[i],0);
}
cout<<Dinic();
//printf("%d",maxflow);
return 0;
}
时间复杂度
匈牙利算法的时间复杂度是(O(n imes m)),但事实上,对于绝大部分的二分图,匈牙利算法都跑不够上限。
(Dinic)的时间复杂度是(O(n imessqrt{m}))
关于网络流的讲解,之后会更新。