带花树算法
序
AC 300祭
正文
主要思想是开花和并查集维护-.-
我们先模拟算法执行的过程
好吧,我并不是绘画流程图的样子。那就老老实实画图吧。
首先,对于每一个点点 bfs。
假设我们有一个图。
我们要在这个图上做一般图匹配。先从一节点开始,他找了二结点,嗯,没有匹配,我们就要路径取反,之后在搞
之后尝试寻找三节点的匹配,然后他找到了一结点,一结点的对应节点的颜色标记为 1 ,并入队。开始寻找2结点,发现他有颜色,且为一,得知是一个奇环,所以我们要先找到树根,然后树根应该是 3
对于 2 1 3 进行开花。此时找到的根应该是 3 我们缩环 2 3 1,二的前驱被标记为 3, 而fa[1],fa[2] 均为3, 但是找不匹配。。于是 34匹配了
然后最后执行九的过程,会有先找4 三入队。然后找 8 ,7入队。然后3是队首还是执行原来的过程1, 2, 3的开花。然后就蒙了可。。。
其实思想是一个用pre来记录路径,然后用 并查集来维护
/*
* @Author: zhltao
* @Date: 2020-04-05 17:26:05
* @Last Modified by: zhltao
* @Last Modified time: 2020-04-05 18:35:20
*/
#include <bits/stdc++.h>
using namespace std;
template <typename T>
inline T read()
{
T x = 0;
char ch = getchar();
bool f = 0;
while(ch < '0' || ch > '9')
{
f = (ch == '-');
ch = getchar();
}
while(ch <= '9' && ch >= '0')
{
x = ((x + (x << 2)) << 1) + (ch - '0');
ch = getchar();
}
return f? -x : x;
}
template <typename T>
void put(T x)
{
if(x < 0)
{
x = -x;
putchar('-');
}
if(x < 10) {
putchar(x + 48);
return;
}
put(x / 10);
putchar(x % 10 + 48);
return ;
}
#define rd read <int>
#define pt(i) put <int> (i), putchar(' ')
typedef long long ll;
typedef double db;
typedef long double ldb;
typedef unsigned long long ull;
typedef unsigned int ui;
const int Maxn = 505, Maxm = 124750 << 1 | 1;
int fa[Maxn], link[Maxn], ans, h[Maxn], cnt, n, m, color[Maxn], pre[Maxn], dfn[Maxn];
queue <int> q;
struct Edge
{
int to, lac;
void insert(int x, int y) { to = y; lac = h[x]; h[x] = cnt++; }
}edge[Maxm];
// 添边
inline void add_edge(int x, int y) { return edge[cnt].insert(x, y), edge[cnt].insert(y, x); }
// 并查集
int find(int x) { return x == fa[x] ? fa[x] : fa[x] = find(fa[x]); }
int ord;
// 求树根
inline int lca(int x, int y)
{
/*
首先如果是花套花
则要求两个分别的树根
再把新的求出来,要不然会造成染色错误
*/
for(++ord, x = find(x), y = find(y); dfn[x] != ord;)
{
//打上时间戳
dfn[x] = ord;
// 对于这个直接就找树根。就是两个交替做
x = find(pre[link[x]]);
// 万一到了增广路的头的话,也是 s
// 找到共同的起点,在一个比如此时 x 已经是 dfn[x] = cnt 了
// 到下边就会 swap 在做一遍
if(y) swap(x, y);
// 退出时在 dfn[x] = ord,即 fa[x] = x 时
}
return x;
}
void boss(int x, int y, int z)
{
// 必须还是要 find
while(find(x) != z)
{
pre[x] = y;
y = link[x];
if(!(color[y] ^ 2))
{
// 在花上且为 T 的点入队,他们可以找匹配
color[y] = 1;
q.push(y);
}
if(!(find(x) ^ x)) fa[x] = z;
// 如果这个点的是树根的话,就把树根的树根改成 z
if(!(find(y) ^ y)) fa[y] = z;
x = pre[y];
}
}
bool bfs(int s)
{
for(int i = 1; i <= n; ++i) fa[i] = i, color[i] = pre[i] = 0;
while(!q.empty()) q.pop();
for(color[s] = 1, q.push(s); !q.empty(); q.pop())
{
int fr = q.front(), to;
for(int i = h[fr]; i != -1; i = edge[i].lac)
{
// 原来在一个花 或这是 T 点
if(find(fr) == find(to = edge[i].to) || color[to] == 2) continue;
// 没打过标记
if(!color[to])
{
// 打上标记,记录前驱
color[to] = 2, pre[to] = fr;
if(!link[to])
{ // 路径取反
for(int x = to, last, y; x; x = last) last = link[y = pre[x]], link[x] = y, link[y] = x;
return 1;
}
// 不行,就入队,打标记
color[link[to]] = 1;
q.push(link[to]);
}
else
{
// 招树根
int z = lca(fr, to);
// 开花
boss(fr, to, z);
boss(to, fr, z);
}
}
}
return 0;
}
int main()
{
#ifdef _DEBUG
freopen("in.txt", "r", stdin);
#endif
n = rd();
m = rd();
memset(h, -1, sizeof h);
while(m--)
add_edge(rd(), rd());
for(int i = 1; i <= n; ++i)
ans += (!link[i] && bfs(i));
put <int> (ans), putchar('
');
for(int i = 1; i <= n; ++i) pt(link[i]);
putchar('
');
return 0;
}
其实我也不太明白qwq
嵬
-.-