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

一般图最大匹配问题


题意:给出一张无向图,求出最大匹配的匹配数。1|V|,|E|103

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

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

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

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

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

算法思想

采用 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搜索树上的 LCA,那么 rtuvrt 形成了一个奇环。

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

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

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

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

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

  • u,v 在搜索树上的 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;
}

出处:https://www.cnblogs.com/Sktn0089/p/17663264.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

posted @   Lgx_Q  阅读(78)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
点击右上角即可分享
微信分享提示