【Learning】带花树——一般图最大匹配

问题

  给定一个图,求该图的最大匹配。即找到最多的边,使得每个点至多属于一条边。

  这个问题的退化版本就是二分图最大匹配。

  由于二分图中不存在奇环,偶环对最大匹配并无影响(可以调整)。所以增广路算法是可以顺利应用的。

  在一般图中,我们还是尝试使用BFS增广路的算法。

  然而一般图中还会出现奇环,在寻找增广路的时候,怎么处理奇环上的冲突?

  目的就是将奇环不断地缩起来(缩花),使得整个图在使用增广算法的时候不受影响,即不会经过奇环。

​  一朵由一个奇环缩点而成,一朵花里面可能还会有花。

  设这个奇环共有\(2k+1\)个点,那么在环内至多可以匹配到\(k\)条边,还会多出一个孤单的点。

  但是,形象地说,这个点可以在环里面自由移动。

  在图上将每个奇环缩成一个点成为一朵花,其实和原图是等价的,为什么?

  因为如果有合法增广路经过这朵花,在交替匹配边的时候,这朵花一定能通过那个自由点适应变化。

  画个图就明白了。

  使用并查集维护花,所有点的代表元指向这朵花里面在这次增广时BFS树中深度最浅的点。

实现

  从每个未匹配的点开始进行BFS,找到一条合法增广路径以后,增广并退出。

  记这个未匹配的点为0类点,之后的点按10交替标序。

  每次在一个0点,枚举下一个点:

  1. 如果下一个点没有匹配,那么就找到了一个增广路,回溯并增广。

  2. 如果下一个点有匹配,那么就把它的匹配点加入队列中。

  设在搜索过程中,搜到连成环的边是\((u,v)\)

  如果连成偶环,不需要理会;如果连成奇环,并且\(u\)\(v\)不在一朵花内,就要对整个奇环缩花了。

  搜到奇环的时候,由于每次从0点枚举下一个点,\(u\)\(v\)都是0点,环一定是这样的:

  首先要求出\(u\)\(v\)的花意义下的\(lca\),它也是\(0\)点。做法是不断暴力向上跳,实际上是两个两个地跳。

  伪代码如下:

int getlca(int x,int y){
	clear visit[];
    x=find(x); y=find(y);
    while(1){
    	if(x){
        	if(x has been visited) return x;
        	visit[x]=1;
      		x=find(pre[match[x]])
    	}
    	swap(x,y);
    }
}

  其中\(match[x]\)记录的是\(x\)的匹配点,而\(pre\)记录的是每个1点的BFS父亲,\(find(x)\)返回\(x\)所属花的代表元。

  广义的讲,\(pre[x]\)的定义是如果\(x\)点失去了当前匹配点,那么它应该匹配谁。

  然后,对整个环缩花,从\((u,v)\)这条边向两边迭代。由于两边情况相同,一个函数调用两次即可:

int lca=getlca(u,v);
blossom(u,v,lca);
blossom(v,u,lca);

​  首先是\(x\)\(y\)\(pre\)要互连,其次是把两个点的并查集的父亲设为\(lca\)(如果它是并查集的代表元,不是的话待会会遍历到的)。

  最后要将环中的1点全部扔进队列里,因为整个环缩起来了以后成为了一个点,要继续作为一个点寻找增广路,等价的做法就是把花里的所有点扔进队列(此时0点已经进过队列了所以不用扔);而缩起来的花是一个0点,故要将所有点的标号设为0(把1点设为0点就好)。

  代码中,用\(s[]\)记录标号。

void blossom(int x,int y,int lca){
	while(find(x)!=lca){
		pre[x]=y;
		if(s[match[x]]==1){
			s[match[x]]=0;
			q.push(match[x]);
		}
		if(fa[x]==x) fa[x]=lca;
		if(fa[match[x]]==match[x]) fa[match[x]]=lca;
		y=match[x];
		x=pre[y];
	}
}

完整代码如下

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
const int N=510,M=125000;
int n,m;
int h[N],tot;
int match[N],s[N],pre[N],vis[N],tim;
int fa[N];
queue<int> q;
struct Edge{int v,next;}g[M*2];
inline void addEdge(int u,int v){
	g[++tot].v=v; g[tot].next=h[u]; h[u]=tot;
	g[++tot].v=u; g[tot].next=h[v]; h[v]=tot;
}
inline int find(int x){return fa[x]==x?x:(fa[x]=find(fa[x]));}
int getlca(int x,int y){
	tim++;
	x=find(x); y=find(y);
	for(;;x^=y^=x^=y)
		if(x){
			if(vis[x]==tim) return x;
			vis[x]=tim;
			x=find(pre[match[x]]);
		}
}
void blossom(int x,int y,int lca){
	while(find(x)!=lca){
		pre[x]=y;
		if(s[match[x]]==1){
			s[match[x]]=0;
			q.push(match[x]);
		}
		if(fa[x]==x) fa[x]=lca;
		if(fa[match[x]]==match[x]) fa[match[x]]=lca;
		y=match[x];
		x=pre[y];
	}
}
int solve(int x){
	for(int i=1;i<=n;i++) fa[i]=i;
	memset(s,-1,sizeof s);
	memset(pre,0,sizeof pre);
	while(!q.empty()) q.pop();	
	s[x]=0;
	q.push(x);
	while(!q.empty()){
		int u=q.front(); q.pop();
		for(int i=h[u],v;i;i=g[i].next){
			v=g[i].v;
			if(s[v]==-1){
				pre[v]=u; 
				s[v]=1;
				if(!match[v]){
					for(int go=1;go;v=go,u=pre[go]){
						go=match[u];
						match[u]=v; match[v]=u;
					}
					return 1;
				}
				s[match[v]]=0;
				q.push(match[v]);
			}
			else if(!s[v]&&find(u)!=find(v)){
				int lca=getlca(u,v);
				blossom(u,v,lca);
				blossom(v,u,lca);
			}
		}
	}
	return 0;
}
int main(){
	scanf("%d%d",&n,&m);
	int u,v;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		addEdge(u,v);
	}
	int ans=0;
	for(int i=1;i<=n;i++)
		if(!match[i])
			ans+=solve(i);
	printf("%d\n",ans);
	for(int i=1;i<=n;i++) printf("%d ",match[i]);
	return 0;
}
posted @ 2018-03-15 08:11  RogerDTZ  阅读(483)  评论(0编辑  收藏  举报