Loading

二分图匹配

kuhn算法 O(|V||E|)

重要定理:Berge 引理:对于图\(G=(V,E)\)和它的一个匹配M,匹配 M 是最大匹配,当且仅当不存在相对于匹配
M 的增广路(感性理解一下增广路就是可以增加最大匹配的路径)。
思想:
1.将图化为左右两边(即二分图染色)
2.对于左边的每个点进行dfs求增广路(即看看是否能再进行匹配)
3.每次求增广路有两种情况(设\(u\in|V_{左部分},v\in|V_{右部分}|\)且\(u\)与\(v\)连通):
1.\(v\)未被匹配->将\(v\)的匹配点改为\(u\)
2.\(v\)已被匹配->寻找是否\(v\)的匹配点可以修改,如果可以那么将v的匹配点修改,然后将\(v\)的匹配点改为\(u\)
第一行是情况1 第二行是情况2

当然情况二有可能更复杂比如:


当然这些只要递归回退就好了,给出代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N_ 1005
ll read()
{
	char ch=getchar();
	ll x=0,f=1;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10+ch-48;
		ch=getchar();
	}
	return x*f;
}
ll ans,tot,V[N_],to[N_],head[N_],m,n,e,match[N_];
bool vis[N_];
void add(int u,int v)
{V[++tot]=v,to[tot]=head[u],head[u]=tot;}
bool find(ll u)
{
	for(int i=head[u];i;i=to[i])
	{
		int v=V[i];
		if(vis[v])continue;
		vis[v]=1;
		if(!match[v]||find(match[v]))
		{
			match[v]=u;
			return 1;
		}
	}
	return 0;  
}
int main()
{
	n=read(),m=read(),e=read();
	for(int i=1;i<=e;i++)
	{
		ll u=read(),v=read()+n; 
		add(u,v);
	}
	for(int i=1;i<=n;i++)
	{
		memset(vis,0,sizeof(vis));
		if(find(i))
		ans++;
	}
	cout<<ans;
}

然后我们发现每次曾广最多只会增加一个匹配,显然不优,因此出现了一些常数优化:
1.对于dfs的vis数组不必每次都清空,因为我们发现每次vis就用于求一条增广路比较浪费,可以多次使用直到求不出增广路;修改后代码:

while(i<=n)
	{
		if(find(i))ans++,i++,f=0;
		else
		{
			if(!f)
			{
				memset(vis,0,sizeof(vis));
				f=1;
			}
			else
			i++;
		}	
	}

2.可以选点数比较少的为左边枚举节点

划归转化为网络流

我们发现即使有过优化复杂度还是太高,注意到我们每次都在寻找增广路,很像求网络流求最大流,于是划归转化:建一个源点s一个汇点t,s到左半边点连一条边权为1边,右半边连一条边权为1的边到t,原来左部到右部的边变为边权为一的有向边,跑一遍dinic,最大流就是最大匹配,每一条满流边就是1个匹配

That's all.欢迎提出质疑

posted @ 2025-11-23 08:26  leaf_ydc  阅读(3)  评论(0)    收藏  举报