Loading

一般图最大匹配——带花树算法

一般图最大匹配问题


题意:给出一张无向图,求出最大匹配的匹配数。\(1\le |V|,|E|\le 10^3\)

我们学过,二分图最大匹配有个算法——匈牙利算法,其主要思想就是枚举起点,不断寻找增广路,然后把找出的路径匹配状态取反,即可多出一个匹配。

当这个图存在奇环时,就不是二分图了,也就不能使用匈牙利算法。

容易发现,我们的增广路径不能经过奇环,否则就不符合匹配的条件了。

奇环的情况非常复杂,如果仍使用 \(\text{DFS}\) 的方法搜索增广路,我们不能保证时间复杂度——因为遇到奇环后不能简单地退出,奇环上的任意一点都可以成为向环外增广的点,时间复杂度可能会炸裂。

\(\text{BFS}\),我们并不能简单判断搜到的路径是否走过一整个奇环。我们需要更强大的算法——带花树算法。

算法思想

采用 \(\text{BFS}\) 的方法。我们要先做没有奇环的情况,所以需要进行黑白染色。设 \(vis[u]\) 表示点 \(u\) 的状态,\(vis[u]=0\) 表示没有访问过,\(vis[u]=1/2\) 表示(黑/白)颜,设 \(mch[u]\) 表示 \(u\) 匹配的点色。根据匈牙利算法的思想,我们只需要把 \(vis[u]=1\) 的点 \(u\) 放进队列里搜索即可。当搜到一条边 \(u,v\) 满足 \(vis[u]=vis[v]=1\),设 \(rt\) 表示 \(u,v\)搜索树上的 \(\text{LCA}\),那么 \(rt-u-v-rt\) 形成了一个奇环。

此时我们搜的增广路径经过了这个奇环,设增光路从这个奇环出来的点为 \(x\)(从 \(x\) 搜出的必要条件为 \(vis[x]=1\)),不难发现,\(x\) 可以取所有点。对于黑点,结论显然成立;对于白点,我们显然可以从这个环的另一边走过来。

考虑把这个奇环上的点的 \(vis\) 全部改成 \(1\),在队列里加入相应的点。记 \(pre[u]\) 表示搜索树上 \(u\) 的前驱(为了简便,我们只考虑 \(vis[u]=2\)\(u\) 的前驱),我们求 \(\text{LCA}\) 和处理奇环上的点都可以暴力搞。

在更一般的情况,我们可能存在“奇环套奇环”的情况,考虑用一个并查集维护,设 \(d[u]\) 表示点 \(u\) 所在的奇环的 \(\text{LCA}\)(严格来说不是 \(\text{LCA}\)……,应该是环上最接近搜索起点的那个点),我们求 \(u,v\)\(\text{LCA}\) 时,每次暴力跳可以先用并查集找到所在奇环的根。

为了简便处理更多情况,在一个奇环上,对于原来非匹配边上的两点 \(x,y\),令 \(pre[x]=y,pre[y]=x\)(具体地……因为当 \(vis[u]=1\) 时我们没有把 \(pre[u]\) 赋值,所以可能会遇到 \(pre[u]=0\) 的情况,我们需要处理这种情况)。

综上,对于处理一个奇环,我们分为两部分:

  • \(u,v\) 在搜索树上的 \(\text{LCA}\)

  • 把奇环上的点的 \(pre\) 值处理一下,把 \(vis=2\) 的点加入队列并改为 \(1\)

ll lca(ll u,ll v) //找 LCA
{
	++T; //第 T 次找 LCA
	u=find(u); v=find(v);
	while(dfn[u]!=T)
	{
		dfn[u]=T; //跳过的点打标记
		u=find(pre[mch[u]]);
		if(v) swap(u,v); //u,v 轮流向上跳
	}
	return u;
}
void shrink(ll u,ll v,ll rt) //更新奇环上的点的信息
{
	while(find(u)!=rt)
	{
		pre[u]=v; v=mch[u]; //更新 vis=1 的点的 pre 值
		if(vis[v]==2) //把 vis=2 的点加入队列并改成 1
		{
			vis[v]=1; q[++r]=v;
		}
		if(find(u)==u) d[u]=rt; // 把奇环里面的点的并查集信息更新
		if(find(v)==v) d[v]=rt;
		u=pre[v];
	}
}

除了处理奇环,当我们找到增广路后,暴力更新路径:

void aug(ll u)
{
	ll tmp;
	while(u)
	{
		tmp=mch[pre[u]];
		mch[u]=pre[u]; mch[pre[u]]=u;
		u=tmp;
	}
}

总代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=1e5+10;
ll n,m,u,v,ans,head[maxn],tot,pre[maxn],mch[maxn],q[maxn],l,r,vis[maxn],d[maxn],dfn[maxn],T;
struct edge
{
	ll v,nxt;
}e[maxn];
void insert(ll u,ll v)
{
	e[++tot]=(edge){v,head[u]};
	head[u]=tot;
}
ll find(ll x)
{
	if(d[x]==x) return x;
	return d[x]=find(d[x]);
}
ll lca(ll u,ll v)
{
	++T;
	u=find(u); v=find(v);
	while(dfn[u]!=T)
	{
		dfn[u]=T;
		u=find(pre[mch[u]]);
		if(v) swap(u,v);
	}
	return u;
}
void shrink(ll u,ll v,ll rt)
{
	while(find(u)!=rt)
	{
		pre[u]=v; v=mch[u];
		if(vis[v]==2)
		{
			vis[v]=1; q[++r]=v;
		}
		if(find(u)==u) d[u]=rt;
		if(find(v)==v) d[v]=rt;
		u=pre[v];
	}
}
void aug(ll u)
{
	ll tmp;
	while(u)
	{
		tmp=mch[pre[u]];
		mch[u]=pre[u]; mch[pre[u]]=u;
		u=tmp;
	}
}
ll bfs(ll s)
{
	for(ll i=1;i<=n;i++) pre[i]=vis[i]=0, d[i]=i;
	q[l=r=1]=s; vis[s]=1;
	while(l<=r)
	{
		ll u=q[l++];
		for(ll i=head[u];i;i=e[i].nxt)
		{
			ll v=e[i].v;
			if(vis[v]==0)
			{
				pre[v]=u;
				if(!mch[v])
				{
					aug(v);
					return 1;
				}
				else
				{
					vis[v]=2; vis[mch[v]]=1;
					q[++r]=mch[v];
				}
			}
			else if(vis[v]==1&&find(u)!=find(v))
			{
				ll rt=lca(u,v);
				shrink(u,v,rt);
				shrink(v,u,rt);
			}
		}
	}
	return 0;
}
int main()
{
	scanf("%lld%lld",&n,&m);
	for(ll i=1;i<=m;i++)
	{
		scanf("%lld%lld",&u,&v);
		insert(u,v);
		insert(v,u);
	}
	for(ll i=1;i<=n;i++)
		if(!mch[i]) ans+=bfs(i);
	printf("%lld\n",ans);
	for(ll i=1;i<=n;i++) printf("%lld ",mch[i]);
	return 0;
}
posted @ 2023-08-28 19:44  Lgx_Q  阅读(48)  评论(1编辑  收藏  举报