[图论]------ 一般图最大匹配(带花树算法)

吐槽

这两天的训练赛接二连三地出这种又冷门又难的算法。
不得不硬刚。。。。
本博客默认阅读者对匈牙利算法有一定理解。

带花树算法

带花树算法,英文: B l o s s o m   a l g o r i t h m Blossom \ algorithm Blossom algorithm,或 E d m o n d s   M a t c h i n g   A l g o r i t h m Edmonds \ Matching \ Algorithm Edmonds Matching Algorithm。是 Jack Edmonds 发表于 1965 年,用于解决一般图最大匹配问题的算法。经过修改后可以得到“一般图最大权匹配算法”。这个算法也证明了,一般图最大匹配有多项式复杂度解法。

从二分图到一般图

大多数人比较熟悉二分图最大匹配的过程,所以理解带花树算法最好还是从匈牙利算法入手。

我们习惯上,把二分图的点分成左右两部分,从每一个左部点(或者右,无所谓)出发,尝试寻找增广路,如果成功找到增广路说明最大匹配可以更新了,同时需要更改左右部点之间的匹配关系。这就是匈牙利算法的大概流程。代码如下所示。

int Hungarian()        //匈牙利最最常见但并不通用的一种写法
{
	int ans = 0;
	memset(link, -1, sizeof(link));
	for(int i=1;i<=N;i++)        //这层循环实际上是枚举二分图其中一个点集的所有点
	{
		memset(vis, 0, sizeof(vis));
		if(dfs(i))               //dfs就是寻找增广路的过程,这里省略细节
		    ans++;
	}
	return ans;
}

如果事先不知道二分图两个集合的结点编号,也就是说我找不到左部点或右部点,可以想到先利用BFS对二分图进行黑白染色,然后从一种颜色出发,向另一种颜色寻找匹配(染色和匈牙利算法可以同时进行)。由于二分图不存在奇环,因此这种方法一定可行。

换句话说:二分图的性质决定了,它天然地将所有结点分好了类。而对于一般图,我们也可以“人为地”,将结点分成两类,从其中一类出发向另一类寻找匹配,然而一般图有可能存在奇环,使得染色出现矛盾的情况。因此我们现在可以得到带花树算法的核心思想:对图中没有奇环的部分按照正常的匈牙利算法处理,并加入对奇环的特殊讨论。

while(!q.empty())
{
	int u = q.front();
	q.pop();
	for(每个可达点)
	{
		int v = edge[i].to;
		if(!vis[v])            //v点未被访问 
		{
			if(!match[v])      //v点没有匹配点 说明找到增广路了 
			{
				agu(v)         //进行增广 
				return 1;      //增广成功更新答案 
			}
			//此处v点有匹配点,则更新交替路 
		}
		else         //判断是否找到奇环,随后写怎么处理 
		{

		}
	}
}

对环的处理

根据BFS的性质可知,我们会得到一棵黑白点交替的树结构。我们规定所有匹配边的入点为“白色”,出点为“黑色”。下面来讨论对奇环的处理。
BFS过程中如果发现奇环,设染色冲突的结点为 u u u v v v,那么 u u u v v v的最近公共祖先一定在这个奇环上,且这个祖先节点一定是“黑色点”,因为匹配边和两种颜色都交替出现,这个结点的子孙结点能形成环说明他自己有不止一个子节点,而这多于一个的子节点必不可能同时为“黑点”,否则祖先节点就同时连了两条匹配边。具体见图。
在这里插入图片描述
对于这个奇环,我们把它缩成一个点(这个点叫做 “花”,这里是这个算法名字的由来),将这个新的点作为一个黑点,向外寻找匹配。我们可以证明缩点之后的图,最大匹配数不变。也就是说:设原图为 G G G,缩点后的图为 G ′ G^{\prime} G

  1. G G G 存在增广路, G ′ G^{\prime} G也存在。
  2. G ′ G^{\prime} G 存在增广路, G G G 也存在。

前面说过偶环内部必有完美匹配,那么奇环内部则一定有一个点可以向环外匹配,环内的每个点都有可能成为这个点,因此缩点之后要把环中的点全部染成黑色并加入队列。

实际实现上,缩点并不需要改变结点间的边关系,只需用并查集维护一个结点是在哪个结点为根的奇环中即可。
完整代码:(洛谷模板)

#include <cstdio>
#include <cstring>
#include <queue>
#define MAXN 1010
using namespace std;
struct t_Edge {
	int next;
	int to;
};
t_Edge edge[MAXN*MAXN*2];
int head[MAXN], num_edge;
int N, M, a, b, ans;
int father[MAXN], pre[MAXN];
int match[MAXN];
int color[MAXN], vis[MAXN];
int clock, top[MAXN], rinedge[MAXN];
queue<int> Q;
void add(int from, int to)
{
	edge[++num_edge].next = head[from];
	edge[num_edge].to = to;
	head[from] = num_edge;
}
int find(int x)
{
	if(father[x]!=x)
	    father[x] = find(father[x]);
	return father[x];
}
int LCA(int x, int y)        //找到最近公共祖先 
{
	clock++;
	while(x)
	{
		rinedge[x] = clock;
		x = find(top[x]);
	}
	x = y;
	while(rinedge[x]!=clock)
	    x = find(top[x]);
	return x;
}
void shrink(int x, int y, int lca)      //将奇环缩成点 并把环中所有点重新染色 
{
	while(find(x)!=find(lca))
	{
		pre[x] = y;
		y = match[x];
		color[y] = 0;
		Q.push(y);
		father[find(x)] = lca;
		father[find(y)] = lca;
		x = pre[y];
	}
}
int blossom(int s)
{
	for(int i=1;i<=N;i++)
	{
		top[i] = pre[i] = color[i] = vis[i] = 0;
		father[i] = i;
	}
	while(!Q.empty())
	    Q.pop();
	vis[s] = 1;
	Q.push(s);
	while(!Q.empty())
	{
		int u = Q.front();
		Q.pop();
		for(int i=head[u];i;i=edge[i].next)
		{
			int v = edge[i].to;
			if(!vis[v])            //v点未被访问 
			{
				top[v] = u;
				pre[v] = u;
				color[v] = 1;
				vis[v] = 1;
				if(!match[v])      //v点没有匹配点 说明找到增广路了 
				{
					int j = v;
					while(j)
					{
						int x = pre[j];
						int y = match[x];
						match[j] = x;
						match[x] = j;
						j = y;
					}
					return 1;
				}
				vis[match[v]] = 1;       //v点有匹配点 需要从匹配点出发继续寻找增广路 
				top[match[v]] = v;
				Q.push(match[v]);
			}
			else if(find(u)!=find(v)&&color[v]==0)      //找到奇环 缩点 
			{
				int lca = LCA(u, v);
				shrink(u, v, lca);
				shrink(v, u, lca);
			}
		}
	}
	return 0;
}
int main()
{
	scanf("%d%d", &N, &M);
	for(int i=1;i<=M;i++)
	{
		scanf("%d%d", &a, &b);
		add(a, b);
		add(b, a);
	}
	for(int i=1;i<=N;i++)
	{
		if(!match[i])
	        ans += blossom(i);
    }
	printf("%d\n", ans);
	for(int i=1;i<=N;i++)
	    printf("%d ", match[i]);
	printf("\n");
	return 0;
}

例题

模板:
洛谷P6113 【模板】一般图最大匹配.

也算模板,建图方式不同:
洛谷P4258 [WC2016]挑战NPC.

一般图最大独立集:
2021年度训练联盟热身训练赛第一场 B - Code Names.

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值