学习笔记第三十四节:一般图最大匹配(带花树算法)

本文深入探讨了带花树算法,一种解决匹配问题中奇环现象的有效方法。通过对比一般图与二分图,详细解释了算法流程,包括点分类、路径搜索与更新规则,特别强调了奇环处理技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

正题

      这节的东西理解起来可能有点难,但是相信这篇博客能够帮到你们。

      首先,我们要发现一般图和二分图的区别。

      很容易就可以知道,当增广路不存在环的时候,和二分图是没有任何差异的。

      我们可以直接用一个没有匹配的点去找点,如果找到的点匹配过,我们就把他们的匹配点放入队列,然后知道找到一个未匹配点,再从头到尾更新增广路。

       但是假如有环呢?

       我们又可以发现,当出现偶环的时候,上面的寻找过程还是成立的。

       出现奇环了呢?发现如果更新增光路,这个奇环的一个点会连着两条匹配边。

       这就不满足题设了。

       怎么办?

       带花树算法正是解决奇环的妙招。

       在算法中,我们把奇环就叫做花。

       我们从一个没有匹配的begin点开始找,并把它标为1类点(tp[begin]=1),找到一个y,假如y没有被匹配过,那么可以直接还原增广路,倒着处理匹配,是的新匹配比原匹配多1.

       假如y没有被找过了。我们把它标为2类点。并把他的匹配点标为1类点,加入队列。

       假如y被找过了,那就要分两种情况讨论。

       1.它是2类点,说明形成了偶环(因为1类点才会放入队列,2类点没有“找”的权利),这时,我们可以不用理这个偶环,因为他不会对题设产生影响(也就是说不会存在增广之后存在一个点两条匹配边)。

       2.它是1类点, 说明形成了奇环,这时这个环就可能对题设产生影响,我们要规定一些东西,才能使它满足题设。

       怎么规定呢?

       在平常的增广路算法中,就是找到路就增广,但在修改匹配的时候,如果经过了一个奇环,可能被夺走的点的匹配点是没有一个前驱的,就是说,在遍历的时候是直接遍历到被夺走的点,变得无路可走。

       我们让两个有边的匹配,头尾互相为前一个即可。

       也就是说,我们使增广路不走奇环。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;

int n,m;
struct edge{
	int y,next;
}s[250010];
int first[510],len=0,match[510],fa[510],tp[510],pre[510],tf[510],tim=0;
queue<int> f;

void ins(int x,int y){
	s[++len]=(edge){y,first[x]};first[x]=len;
}

int findpa(int x){
	if(fa[x]!=x) return fa[x]=findpa(fa[x]);
	return x;
}

int lca(int x,int y){
	for(tim++;;swap(x,y))
		if(x){
			x=findpa(x);
			if(tf[x]==tim) return x;
			tf[x]=tim;x=pre[match[x]];
		}
}

void make(int x,int y,int t){
	while(findpa(x)!=t){
		pre[x]=y;y=match[x];
		if(tp[y]==2) tp[y]=1,f.push(y);
		fa[x]=t;fa[y]=t;
		x=pre[y];
	}
}

bool Aug(int begin){
	for(int i=1;i<=n;i++) fa[i]=i;
	memset(tp,0,sizeof(tp));memset(pre,0,sizeof(pre));
	while(!f.empty()) f.pop();
	tp[begin]=1;
	f.push(begin);
	while(!f.empty()){
		int x=f.front();f.pop();
		for(int i=first[x];i!=0;i=s[i].next){
			int y=s[i].y;
			if(findpa(x)==findpa(y) || tp[y]==2) continue;
			if(!tp[y]){
				tp[y]=2;pre[y]=x;
				if(!match[y]){
					for(int last;x;y=last,x=pre[last]) last=match[x],match[x]=y,match[y]=x;
					return true;
				}
				tp[match[y]]=1;f.push(match[y]);
			}
			else{
				int op=lca(x,y);
				make(x,y,op);make(y,x,op);
				
			}
		}
	}
	return false;
}

int main(){
	scanf("%d %d",&n,&m);
	int x,y;
	for(int i=1;i<=m;i++){
		int x,y;
		scanf("%d %d",&x,&y);
		ins(x,y);ins(y,x);
	}
	int ans=0;
	for(int i=1;i<=n;i++) ans+=(!match[i] && Aug(i));
	printf("%d\n",ans);
	for(int i=1;i<=n;i++) printf("%d ",match[i]);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值