一般图最大匹配(带花树算法)(学习+模板)

参考博客:

https://blog.csdn.net/xuezhongfenfei/article/details/10148445

https://www.cnblogs.com/zhoushuyu/p/8717234.html

https://www.cnblogs.com/owenyu/p/6858508.html

经典例题:

https://www.cnblogs.com/BAJimH/p/10569418.html

增广路取反:二分图匹配中增广路的顺序为非匹配边->-匹配边>非匹配边->匹配边.>...->非匹配边。取反则为将非匹配边变为匹配边,匹配边变为非匹配边。

自己理解:核心算法还是找增广路,但是由于一般图中有可能存在奇环,那样我们找增广路时,可能会涉及一条边两次。这样就不能通过增广路取反,从而增加匹配的数量。注意到如果一个奇环中有k(k>=3)个点,那么,若内部点匹配的话,就会多出一个点无法匹配,怎么办呢?找环外的点匹配。那么对于一个奇环,他只有一个点没有匹配到,我们就把奇环缩成一个点(俗称开花)。怎么缩点呢?,这里我们用并查集维护。但是,由于无法确定用奇环中的哪个点去找环外点才能得到最大匹配,要把环中的点枚举一遍。而且有可能出现环中有环(花中有花)的情况,我们每次找到增广路后,还需要把环一层层展开。

具体的流程为(粘的上面大佬博客里的):

我们给所有点黑白染色。假设开始增广的点是黑点。把所有黑点压进队列中顺次处理。对于一个黑点u,找与他相邻的点v,会出现一下四种情况:

1、u,v已经被缩成一个点了(这两个点在一朵花里),跳过。

2、v是白点,说明已经被匹配了,也就是偶环,跳过。

3、v还没有被染色。那就先把这个点染成白的,然后尝试与u匹配。如果v还没有匹配就匹配上,增广成功,然后沿增广路取反。如果v已经被匹配了,那么匹配他的点就是个黑点,染色,然后压进队列。

4、v也是黑点。这时候染色发生了冲突,说明遇见了奇环。这时候就需要找到两个点在带花树中的lca,然后把这整个环缩成一个点。(直接开花。)

缩点(开花)过程:
1、找x和y的LCA(的根)L。找LCA可以用各种方法。。。直接朴素也行。
2、在pre数组中把x和y接起来(表示它们形成环了!)
3、从x、y分别走到L,维护并查集使得变成一棵树,同时沿路把pre数组连接起来。

例题1:uoj29 #79. 一般图最大匹配

求一般图最大匹配,并输出方案。

#include 
#define ll long long
using namespace std;
const int N = 510;
const int M = 3e5+10;
struct node{ int to,nxt; }g[M];
int head[N],cnt;
int vis[N],match[N],f[N],pre[N],Id,id[N];
//vis[i]: 0(未染色) 1(黑色) 2(白色)
//match[i]: i的匹配点
//f[i]: i在带花树中的祖先
//pre[i]: i的非匹配边的另一点 
//id: 找LCA用 
int n,m,ans,u,v;
queue q;
void Init()
{
	Id=ans=cnt=0;
	for(int i=1;i<=n;i++)
		head[i]=-1,id[i]=match[i]=0;
}
void add(int u,int v){ g[cnt]=node{v,head[u]},head[u]=cnt++; }
int getf(int x){ return f[x]==x?x:f[x]=getf(f[x]); }
//查询x和y在带花树中的LCA 
int LCA(int x,int y)
{
	//沿着增广路向上找lca 
	for(++Id;;swap(x,y))//x,y交替向上 
		if(x)
		{
			x=getf(x);//有可能环中有环(花中有花),所以用并查集找祖先,只处理祖先节点 
			if(id[x]==Id) return x; //x,y在同一环中,一定会找到已被编号的点,该点即为LCA。 
			else id[x]=Id,x=pre[match[x]];//给点编号,并沿着非匹配边向上找		
		}
}
//缩点(开花),将x、y到LCA(l)路径中的点,缩为一点 
void blossom(int x,int y,int l)
{ 
	while(getf(x)!=l)
	{
		//增广路取反 
		pre[x]=y,y=match[x];
		//如果x、y的奇环中有白点,将其染为黑点,放入队列,让其去找不是环中的匹配点	
		if(vis[y]==2) vis[y]=1,q.push(y);
		//只改变是根的点 
		if(getf(x)==x) f[x]=l;
		if(getf(y)==y) f[y]=l;
		//增广路取反 
		x=pre[y];
	} 
}
bool aug(int s)
{
	//每次都以s为起点bfs,建带花树 
	for(int i=1;i<=n;i++)
		vis[i]=pre[i]=0,f[i]=i;	
	while(!q.empty()) q.pop();
	
	q.push(s),vis[s]=1;
	while(!q.empty())
	{
		u=q.front();q.pop();
		for(int i=head[u];~i;i=g[i].nxt)
		{
			v=g[i].to;
			//如果已经在同一个环(花)中或者是白点(意为这已经有匹配点),只接跳过 
			//这种情况不会增加匹配数 
			if(getf(u)==getf(v)||vis[v]==2) continue;
			//如果没有被染色 
			if(!vis[v])
			{
				//先染为白色,将前继点指向u 
				vis[v]=2,pre[v]=u;
				//如果没有被匹配过,直接匹配成功 
				if(!match[v])
				{
					//增广路取反 
					for(int x=v,last;x;x=last)
						last=match[pre[x]],match[x]=pre[x],match[pre[x]]=x;		
					return 1;
				}
				//如果被匹配过,则把匹配v的点染为黑色,放入队列中	
				vis[match[v]]=1,q.push(match[v]);
			}
			//v是黑色,形成奇环,则缩点(开花)。 
			else 
			{
				 
				int lca=LCA(u,v);
				blossom(u,v,lca),blossom(v,u,lca);
			}
		}	
	}
	return 0;
}
int main(void)
{
	while(~scanf("%d%d",&n,&m))	
	{
		Init();
		while(m--)
		{
			scanf("%d%d",&u,&v);
			add(u,v),add(v,u);	
		}        
		for(int i=1;i<=n;i++)
			if(!match[i]&&aug(i))
				ans++;	
		printf("%d\n",ans);
		for(int i=1;i<=n;i++)
			printf("%d%c",match[i]," \n"[i==n]);
	}
	return 0;	
} 

例题2:zoj3316 Game

题意:一个棋盘上有n个棋子,两个人做游戏,拿的棋子和上一个被拿的棋子的曼哈顿距离不能超过L,问后手能否赢。

思路:最大匹配等于n的时候才能赢,不然就会输。

#include 
#define ll long long
using namespace std;
const int N = 400;
const int M = 2e5+10;
struct node{ int to,nxt; }g[M];
struct Node{ int x,y; }a[N];
int head[N],cnt;
int vis[N],pre[N],f[N],match[N],id[N],Id;
queue q;
int n,L,ans,u,v;
void Init()
{
	ans=Id=cnt=0;
	for(int i=1;i<=n;i++)
		head[i]=-1,id[i]=match[i]=0;
}

void add(int u,int v) { g[cnt]=node{v,head[u]},head[u]=cnt++; }
int getf(int x){ return f[x]==x?x:f[x]=getf(f[x]); };
int LCA(int x,int y)
{
	for(++Id;;swap(x,y))
		if(x)
		{
			x=getf(x);
			if(id[x]==Id) return x;
			else id[x]=Id,x=pre[match[x]];
		}
}
void blossom(int x,int y,int l)
{
	while(getf(x)!=l)
	{
		pre[x]=y,y=match[x];
		if(vis[y]==2) vis[y]=1,q.push(y);
		if(getf(x)==x) f[x]=l;
		if(getf(y)==y) f[y]=l;
		x=pre[y];
	}
	
}

bool bfs(int s)
{
	for(int i=1;i<=n;i++)
		vis[i]=pre[i]=0,f[i]=i;
	while(!q.empty()) q.pop();
	q.push(s);vis[s]=1;
	while(!q.empty())
	{
		u=q.front();q.pop();
		for(int i=head[u];~i;i=g[i].nxt)
		{
			v=g[i].to; 
			if(getf(u)==getf(v)||vis[v]==2) continue;
			if(!vis[v])
			{
				vis[v]=2,pre[v]=u;
				if(!match[v])
				{
					for(int x=v,last;x;x=last)
						last=match[pre[x]],match[x]=pre[x],match[pre[x]]=x;
					return 1;
				}
				vis[match[v]]=1,q.push(match[v]);
			}
			else
			{
				int lca=LCA(u,v);
				blossom(u,v,lca),blossom(v,u,lca);
			}
		}
	}
	return 0;
}
int main(void)
{
	while(~scanf("%d",&n))
	{
		Init();
		for(int i=1;i<=n;i++)
			scanf("%d%d",&a[i].x,&a[i].y);	
		scanf("%d",&L);
		for(int i=1;i<=n;i++)
			for(int j=1;j

 

 

你可能感兴趣的:(=====图论=====,=====模板=====)