原文链接:http://fanhq666.blog.163.com/blog/static/8194342620120304463580/
它的核心思想还是找增广路。假设已经匹配好了一堆点,我们从一个没有匹配的节点s开始,使用BFS生成搜索树。每当发现一个节点u,如果u还没有被匹配,那么就可以进行一次成功的增广;否则,我们就把节点u和它的配偶v一同接到树上,之后把v丢进队列继续搜索。我们给每个在搜索树上的点一个类型:S或者T。当u把它的配偶v扔进队列的时候,我们把u标记为T型,v标记为S型。于是,搜索树的样子是这样的:
s/ \
a b
| |
c d
/ \ / \
e f u j
| | | |
i j v k
其中,黑色竖线相连的两个点是已经匹配好的,蓝色斜线表示两个点之间有边,但是没有配对。T型的用红色,S型的用黑色。
这里有个小问题:一个S型点d在某一步扩展的时候发现了点u,如果u已经在搜索树上了(即,出现了环),怎么办?
我们规定,如果u的类型是T型,就无视这次发现;(这意味着我们找到了一个长度为偶数的环,直接无视)
s
/ \
a b
| |
c d 如果连出来的边是指向T型点的,就无视这个边。
/ \ / \
e f<- g
| | |
i j k
否则,我们找到了一个长度为奇数的环,就要进行一次“缩花”的操作!所谓缩花操作,就是把这个环缩成一个点。
s
/ \
a b
| |
c d
/ \ / \
e f | g
| | | |
i u<-+ k
这个图缩花之后变成了5个点(一个大点,或者叫一朵花,加原来的4个点):
缩点完成之后,还要把原来环里面的T型点统统变成S型点,之后扔到队列里去。
+-------------+
| |
| s |
| / \ |
| a b |
| | | | 现在是一个点了!还是一个S点。
| c d |
| / \ / \ |
+-|-- f--u ---|---+
| | | |
| | | |
| | | |
| +-------------+ |
| |
e g
| |
i k
为什么能缩成一个点呢?我们看一个长度为奇数的环(例如上图中的s-b-d-j-f-c-a-),如果我们能够给它中的任意一个点找一个出度(配偶),那么环中的其他点正好可以配成对,这说明,每个点的出度都是等效的。例如,假设我们能够给图中的点d另找一个配偶(例如d'好了),那么,环中的剩下6个点正好能配成3对,一个不多,一个不少(算上d和d'一共4对刚刚好)。
a-s-b-d-d' a s-b d-d'
\ | => \
c-f-u c f-u
这就是我们缩点的思想来源。有一个劳苦功高的计算机科学家证明了:缩点之前和缩点之后的图是否有增广路的情况是相同的。
缩起来的点又叫一朵花(blossom).
注意到,组成一朵花的里面可能嵌套着更小的花。
当我们最终找到一条增广路的时候,要把嵌套着的花层层展开,还原出原图上的增广路出来。
嗯,现在你对实现这个算法有任何想法吗?
天啊,还要缩点……写死谁。。。。。。
我一开始也是这么想的。
我看了一眼网上某个大牛的程序,之后结合自己的想法,很努力地写出了一个能AC的版本。
实现的要点有什么呢?
首先,我们不“显式”地表示花。我们记录一个Next数组,表示最终增广的时候的路径上的后继。同时,我们维护一个并查集,表示每个点现在在以哪个点为根的花里(一个花被缩进另一朵花之后就不算花了)。还要记录每个点的标记。
主程序是一段BFS。对于每个由x发展出来的点y,分4种情况讨论:
1。xy是配偶(不说夫妻,这是非二分图。。。)或者xy现在是一个点(在一朵花里):直接无视
2。y是T型点:直接无视
3。y目前单身:太好了,进行增广!
4。y是一个S型点:缩点!缩点!
缩点的时候要进行的工作:
1。找x和y的LCA(的根)p。找LCA可以用各种方法。。。直接朴素也行。
2。在Next数组中把x和y接起来(表示它们形成环了!)
3。从x、y分别走到p,修改并查集使得它们都变成一家人,同时沿路把Next数组接起来。
Next数组很奇妙。每时每刻,它实际形成了若干个挂在一起的双向链表来表示一朵花内部的走法。
----
/ \<--+
| | |
| |<--+
v v
----------
/ \
+- --+
| |
| |
+---->s <------+
然后就在网上找找看有没有神马模板代码之类的,在http://twinsclover.is-programmer.com/posts/21598.html的博客里找到一个
- #include <iostream>
- #include <stdio.h>
- #include <string.h>
- #include <cmath>
- #include <algorithm>
- #include <queue>
- using namespace std;
- #define MAXE 250*250*2
- #define MAXN 250
- #define SET(a,b) memset(a,b,sizeof(a))
- deque<int> Q;
- //g[i][j]存放关系图:i,j是否有边,match[i]存放i所匹配的点
- bool g[MAXN][MAXN],inque[MAXN],inblossom[MAXN];
- int match[MAXN],pre[MAXN],base[MAXN];
- //找公共祖先
- int findancestor(int u,int v)
- {
- bool inpath[MAXN]={false};
- while(1)
- {
- u=base[u];
- inpath[u]=true;
- if(match[u]==-1)break;
- u=pre[match[u]];
- }
- while(1)
- {
- v=base[v];
- if(inpath[v])return v;
- v=pre[match[v]];
- }
- }
- //压缩花
- void reset(int u,int anc)
- {
- while(u!=anc)
- {
- int v=match[u];
- inblossom[base[u]]=1;
- inblossom[base[v]]=1;
- v=pre[v];
- if(base[v]!=anc)pre[v]=match[u];
- u=v;
- }
- }
- void contract(int u,int v,int n)
- {
- int anc=findancestor(u,v);
- SET(inblossom,0);
- reset(u,anc);reset(v,anc);
- if(base[u]!=anc)pre[u]=v;
- if(base[v]!=anc)pre[v]=u;
- for(int i=1;i<=n;i++)
- if(inblossom[base[i]])
- {
- base[i]=anc;
- if(!inque[i])
- {
- Q.push_back(i);
- inque[i]=1;
- }
- }
- }
- bool dfs(int S,int n)
- {
- for(int i=0;i<=n;i++)pre[i]=-1,inque[i]=0,base[i]=i;
- Q.clear();Q.push_back(S);inque[S]=1;
- while(!Q.empty())
- {
- int u=Q.front();Q.pop_front();
- for(int v=1;v<=n;v++)
- {
- if(g[u][v]&&base[v]!=base[u]&&match[u]!=v)
- {
- if(v==S||(match[v]!=-1&&pre[match[v]]!=-1))contract(u,v,n);
- else if(pre[v]==-1)
- {
- pre[v]=u;
- if(match[v]!=-1)Q.push_back(match[v]),inque[match[v]]=1;
- else
- {
- u=v;
- while(u!=-1)
- {
- v=pre[u];
- int w=match[v];
- match[u]=v;
- match[v]=u;
- u=w;
- }
- return true;
- }
- }
- }
- }
- }
- return false;
- }
- int solve(int n)
- {
- SET(match,-1);
- int ans=0;
- for(int i=1;i<=n;i++)
- if(match[i]==-1&&dfs(i,n))
- ans++;
- return ans;
- }