二分图相关

概念

对于一个图\(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\n",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 \times m)\),但事实上,对于绝大部分的二分图,匈牙利算法都跑不够上限。
\(Dinic\)的时间复杂度是\(O(n \times\sqrt{m})\)
关于网络流的讲解,之后会更新。

posted @ 2019-05-24 18:53  __Liuz  阅读(315)  评论(0编辑  收藏  举报