【luogu P6113】【UOJ 79】【模板】一般图最大匹配(带花树)

【模板】一般图最大匹配

题目链接:luogu P6113 / UOJ 79

题目大意

给你一个图,问你它的最大匹配。

思路

首先我们要知道带花树其实是在匈牙利的匹配算法上,特殊处理掉有奇环的情况。

那如果有奇环是怎么样的呢?
那我们也处理不了它,那我们就考虑把它缩成一个点,然后继续搞。

那我们可以这么搞,然后搞完之后,再把环展开。

那如何实现呢?
我们考虑搞一个队列,让队列的点都是黑色点,它的匹配点是白色点,其它没有颜色,那如果出现两个相邻同时黑色,那就要开花了(缩点)。

那要怎么开花呢,我们不能重构图,复杂度会很大,我们考虑用一个并查集进行维护,把同一个花中的点在并查集中合并。
那我们找环就是找你出问题的那两个点在 bfs 树上的 LCA,这个其实一步一步轮流跳就可以了。

然后接着考虑求出来了怎么开花。
我们考虑把 \(pre_i\) 这个东西叫做它的前驱,那如果我们出问题的点是 \(u,v\),我们就把它的 \(pre\) 互相连接。然后两边分别跳,把白色的点全部变成黑色的,然后扔进队列里面就可以了。
(因为这样后面就是两种路线都分别可以有路扩展了)

代码

#include<queue>
#include<cstdio>

using namespace std;

struct node {
	int to, nxt;
}e[100001];
int n, m, x, y, le[1001], KK;
int pp[1001], ans, fa[1001], tim;
int kd[1001], pre[1001], dfn[1001];
queue <int> q;

void add(int x, int y) {
	e[++KK] = (node){y, le[x]}; le[x] = KK;
	e[++KK] = (node){x, le[y]}; le[y] = KK;
}

int find(int x) {//并查集
	if (fa[x] == x) return x;
	return fa[x] = find(fa[x]);
}

int lca(int x, int y) {
	++tim;
	x = find(x); y = find(y);
	while (dfn[x] != tim) {//不停暴力往上跳
		dfn[x] = tim;
		x = find(pre[pp[x]]);
		if (y) swap(x, y);//交替跳
	}
	return x;
}

void blossom(int x, int y, int w) {
	while (find(x) != w) {
		pre[x] = y; y = pp[x];
		if (kd[y] == 2) kd[y] = 1, q.push(y);//强改颜色且入队
		if (find(x) == x) fa[x] = w;//连到根
		if (find(y) == y) fa[y] = w;
		x = pre[y];
	}
}

int wk(int x) {
	for (int i = 1; i <= n; i++)
		fa[i] = i, kd[i] = pre[i] = 0;
	while (!q.empty()) q.pop();
	
	q.push(x); kd[x] = 1;
	while (!q.empty()) {
		x = q.front();
		q.pop();
		
		for (int i = le[x]; i; i = e[i].nxt) {
			if (find(x) == find(e[i].to) || kd[e[i].to] == 2) continue;//在同一个花里面或形成了偶环
			
			if (!kd[e[i].to]) {
				kd[e[i].to] = 2; pre[e[i].to] = x;
				if (!pp[e[i].to]) {
					for (int k = e[i].to; k;) {//匈牙利的匹配过程
						int ty = pp[pre[k]];
						pp[k] = pre[k]; pp[pre[k]] = k;
						k = ty;
					}
					return 1;
				}
				kd[pp[e[i].to]] = 1; q.push(pp[e[i].to]);
			}
			else {//出现奇环
				int w = lca(x, e[i].to);//找到花根
				blossom(x, e[i].to, w);//两边反向展开
				blossom(e[i].to, x, w);
			}
		}
	}
	return 0;
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d %d", &x, &y);
		add(x, y);
	}
	
	for (int i = 1; i <= n; i++)
		if (!pp[i]) ans += wk(i);
	
	printf("%d\n", ans);
	for (int i = 1; i <= n; i++) printf("%d ", pp[i]);
	
	return 0;
}
上一篇:Redis数据类型


下一篇:修改用户uid