带花树算法--一般图最大匹配
2017-07-15 16:12
453 查看
可以在uoj#79测试模板题
如标题,简单地介绍一下带花树算法,提供一个自认为不错的模板
安利一篇介绍得很详细的blog
由于本人实在太蒻。。。这篇blog就不讲述任何关于算法正确性的证明吧
(也许以后会有兴趣翻译原论文:EfficientAlgorithmsforFindingMaximalMatchinginGraphs,不过现在肯定是懒了。。)
可能有些选手会想苟蒻我一样懒,二分图匹配只会用Dinic做法,建议学习带花树之前看一下匈牙利算法
就像匈牙利算法一样,带花树算法的核心也是类似地每次找增广路
假设对于原图的k个点,已经找到了一些匹配,先把这些匹配留着
选择一个尚未匹配的点s,作为起点,进行bfs,保留bfs树
每次遍历到一个点,如果它也没匹配,那非常棒!因为这样就找到了一条增广路,直接增广即可。
否则,记这个点为u,记它的配偶为v
对点u,标记为T节点,对于v,标记为S节点,并把v入队,继续bfs
(带上起点s,显然,每时每刻在队列中的点都是S类节点)
那么得到的bfs树画出来大概就是这样的
其中红色节点为起点
显然,有的时刻,从某个点出发,路径上下一个点是被访问过的,哪怎么办?
对这个节点分类讨论
1.这个点是T节点
就像图中蓝线连接的边,显然,此时找到了一个偶环,忽略即可
2.这个点是S节点
还是图中的蓝线,此时就找到了一个长度为奇数的环
不过这时候的情况并不简单。。。显然,找不到一个方案构造增广路
但是,如果这个环上存在任意一个点,使得这个点连接的点中存在一个未匹配点
那么显然,此时可以构造出一条一路回溯的增广路
那么,这个时候这个奇环上任意一个点有边连接未匹配点都是等价的
于是,可以将这个奇环缩成一个新点,然后再继续进行bfs,直到找到增广路为止(或是证明不存在)
至此,带花树算法求解一般图匹配的基本步骤就大致清晰了
bfs->缩环->bfs->缩环->…->缩环->bfs->找到增广路
那么找到增广路时,就是沿路将匹配情况取反,以及,我们需要把沿途经过的环(带花树的花)一个个展开。。。???
完全没有写的欲望??不知代码从何下手???
这边提供一个本人认为较容易实现的板子。。(反正这个算法都是靠板子?)
首先,对于每次选择的bfs起点,1~n for一遍就行了
每次判断当前点是否已配对,如果没有,才进行bfs
对于每个遍历到的T类点,记录pre[i]为找到它的S类点编号
每次出现S找到S的情况就需要进行缩环
对于缩出来的环,只找一个环上的S点作为它的代表点
如果一朵花被包含在另一朵花内,那么这朵花就不再表示出来了
其实我们不需要真正地去做这个缩环的过程,
只需要对每朵花内的pre数组进行适当修改就行了
大概如图所示:
这是一个S找到S的情形
只需要大力将每个S的pre边连上就行了
显然,如果一个点找到了合法的匹配对象(即一个未匹配点)
我们可以很轻松的通过pre边把需要修改的环修改掉
具体操作不妨参考本人代码中的函数
Link为缩环函数
Rebuild为上述需要更改的pre边
Augment则为增广函数
如标题,简单地介绍一下带花树算法,提供一个自认为不错的模板
安利一篇介绍得很详细的blog
由于本人实在太蒻。。。这篇blog就不讲述任何关于算法正确性的证明吧
(也许以后会有兴趣翻译原论文:EfficientAlgorithmsforFindingMaximalMatchinginGraphs,不过现在肯定是懒了。。)
可能有些选手会想苟蒻我一样懒,二分图匹配只会用Dinic做法,建议学习带花树之前看一下匈牙利算法
就像匈牙利算法一样,带花树算法的核心也是类似地每次找增广路
假设对于原图的k个点,已经找到了一些匹配,先把这些匹配留着
选择一个尚未匹配的点s,作为起点,进行bfs,保留bfs树
每次遍历到一个点,如果它也没匹配,那非常棒!因为这样就找到了一条增广路,直接增广即可。
否则,记这个点为u,记它的配偶为v
对点u,标记为T节点,对于v,标记为S节点,并把v入队,继续bfs
(带上起点s,显然,每时每刻在队列中的点都是S类节点)
那么得到的bfs树画出来大概就是这样的
其中红色节点为起点
显然,有的时刻,从某个点出发,路径上下一个点是被访问过的,哪怎么办?
对这个节点分类讨论
1.这个点是T节点
就像图中蓝线连接的边,显然,此时找到了一个偶环,忽略即可
2.这个点是S节点
还是图中的蓝线,此时就找到了一个长度为奇数的环
不过这时候的情况并不简单。。。显然,找不到一个方案构造增广路
但是,如果这个环上存在任意一个点,使得这个点连接的点中存在一个未匹配点
那么显然,此时可以构造出一条一路回溯的增广路
那么,这个时候这个奇环上任意一个点有边连接未匹配点都是等价的
于是,可以将这个奇环缩成一个新点,然后再继续进行bfs,直到找到增广路为止(或是证明不存在)
至此,带花树算法求解一般图匹配的基本步骤就大致清晰了
bfs->缩环->bfs->缩环->…->缩环->bfs->找到增广路
那么找到增广路时,就是沿路将匹配情况取反,以及,我们需要把沿途经过的环(带花树的花)一个个展开。。。???
完全没有写的欲望??不知代码从何下手???
这边提供一个本人认为较容易实现的板子。。(反正这个算法都是靠板子?)
首先,对于每次选择的bfs起点,1~n for一遍就行了
每次判断当前点是否已配对,如果没有,才进行bfs
对于每个遍历到的T类点,记录pre[i]为找到它的S类点编号
每次出现S找到S的情况就需要进行缩环
对于缩出来的环,只找一个环上的S点作为它的代表点
如果一朵花被包含在另一朵花内,那么这朵花就不再表示出来了
其实我们不需要真正地去做这个缩环的过程,
只需要对每朵花内的pre数组进行适当修改就行了
大概如图所示:
这是一个S找到S的情形
只需要大力将每个S的pre边连上就行了
显然,如果一个点找到了合法的匹配对象(即一个未匹配点)
我们可以很轻松的通过pre边把需要修改的环修改掉
具体操作不妨参考本人代码中的函数
Link为缩环函数
Rebuild为上述需要更改的pre边
Augment则为增广函数
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<vector> #include<queue> #include<set> #include<map> #include<stack> #include<bitset> using namespace std; const int N = 505; int n,m,cnt,Num[105][7],fa ,pre ,Mark ,mat ,vis ; queue <int> Q; vector <int> v ; inline int Getfa(int x) { return x == fa[x] ? x : fa[x] = Getfa(fa[x]); } inline int getint() { char ch = getchar(); int ret = 0; while (ch < '0' || '9' < ch) ch = getchar(); while ('0' <= ch && ch <= '9') ret = ret * 10 + ch - '0',ch = getchar(); return ret; } void Build() { n = getint(); m = getint(); while (m--) { int x = getint(),y = getint(); v[x].push_back(y); v[y].push_back(x); } } inline void Augment(int p) { while (p != -1) { int tmp = mat[pre[p]]; mat[p] = pre[p]; mat[pre[p]] = p; p = tmp; } } inline void Rebuild(int x,int y,int lca) { while (Getfa(x) != lca) { pre[x] = y; if (fa[x] == x) fa[x] = lca; if (fa[mat[x]] == mat[x]) fa[mat[x]] = lca; if (Mark[mat[x]] == 1) {Q.push(mat[x]); Mark[mat[x]] = 0;} y = mat[x]; x = pre[y]; } } inline void Link(int x,int y) { ++cnt; int p = Getfa(x); for (;;) { vis[p] = cnt; p = mat[p]; if (p == -1) break; p = Getfa(pre[p]); } int lca; p = Getfa(y); for (;; p = Getfa(pre[mat[p]])) if (vis[p] == cnt) {lca = p; break;} Rebuild(x,y,lca); Rebuild(y,x,lca); } inline int BFS(int s) { for (int i = 1; i <= n; i++) fa[i] = i,Mark[i] = pre[i] = -1; while (!Q.empty()) Q.pop(); Mark[s] = 0; Q.push(s); while (!Q.empty()) { int k = Q.front(); Q.pop(); for (int i = 0; i < v[k].size(); i++) { int to = v[k][i]; if (Getfa(to) == Getfa(k)) continue; if (Mark[to] == -1) { Mark[to] = 1; pre[to] = k; if (mat[to] == -1) { Augment(to); return 1; } Mark[mat[to]] = 0; Q.push(mat[to]); } else if (Mark[to] == 0) Link(to,k); } } return 0; } int main() { #ifdef DMC freopen("DMC.txt","r",stdin); #endif int Ans = 0; Build(); for (int i = 1; i <= n; i++) mat[i] = -1; for (int i = 1; i <= n; i++) if (mat[i] == -1) Ans += BFS(i); cout << Ans << endl; for (int i = 1; i <= n; i++) printf("%d%c",mat[i] == -1 ? 0 : mat[i],i == n ? '\n' : ' '); return 0; }
相关文章推荐
- 一般图最大匹配问题-带花树开花算法
- URAL 1099 Work scheduling 一般图的最大匹配 带花树算法(模板)
- Ural 1099 Work Scheduling (一般图的最大匹配:带花树算法)
- 一般图最大匹配--带花树算法
- 一般图最大匹配--带花树算法
- poj 3020 一般图最大匹配 带花树开花算法
- #79. 一般图最大匹配(带花树算法)
- uoj #79. 一般图最大匹配 带花树算法
- 利用带花树算法解决一般图的最大匹配
- UOJ 一般图的最大匹配(带花树算法模板)
- 带花树算法 UOJ#79. 一般图最大匹配
- 一般图最大匹配——带花树算法
- 一般图最大匹配问题-带花树开花算法
- [模板]带花树算法(一般图最大匹配)
- 一般图最大匹配 带花树算法 模板
- HDOJ 4687 Boke and Tsukkomi 一般图最大匹配带花树+暴力
- 一般图匹配--带花树算法
- Work Scheduling URAL - 1099 一般图的最大匹配(带花树)
- uoj#79. 一般图最大匹配【带花树模板】
- 【UOJ#97】一般图最大匹配【带花树开花】