带花树
带花树是一种求解一般图最大匹配的算法。时间复杂度上限 O ( n 3 ) O(n^3) O(n3)。
首先推荐一篇很好理解的博客。
一般图相较于二分图就是图上多了奇环,而“花”指的就是图中的奇环。对于奇环的匹配:首先在环内尽可能地匹配显然是最优的,若环上有 2 k + 1 2k+1 2k+1个点,则环内可以用 k k k条边匹配任意 2 k 2k 2k个点,剩下一个未匹配的点可以向外匹配,这个未匹配点是根据需要回溯时才确定的,所以将奇环缩点,用并查集维护是否在同一个“花”内。
具体来说,我们每次选择一个未匹配点 b f s bfs bfs进行增广(将该点标记为 S S S),并将所有访问到的点标记为 S S S或 T T T(出发点标记为 S S S)。为了回溯连边还需要用到 p r e pre pre数组,对于每个标记为 T T T的点 i i i, p r e i pre_i prei表示当前 b f s bfs bfs中 i i i点第一次是被哪个结点访问到的。
一颗
b
f
s
bfs
bfs树形如:

其中起点为
s
s
s,黑点标记为
S
S
S,红点标记为
T
T
T。
S
,
T
S,T
S,T标记则相当于二分图匹配中的出度和入度一样。
如同二分图匹配,每次只增广 S S S标记点,将需要增广的点逐个压入队列(一开始队列中只有起点 S S S)依次处理。
如果 d f s dfs dfs的话不能之间判断增广点的类型,甚至可能增广到一个首尾都是自己的奇环,所以 b f s bfs bfs做的实际上是一个给点定型的操作,缩点后转成二分图。
模拟一下从当前点 x x x向外扩展到 v v v的情况:
-
x
x
x和
v
v
v在同一个“花”内,或者
v
v
v有标记
T
T
T(偶环),跳过
偶环情况如图:

-
v
v
v没有标记,将
v
v
v标记为
T
T
T
2.1. v v v已匹配,则将它的匹配点 m a t c h v match_v matchv标记为 S S S,加入 b f s bfs bfs队列进行增广
所以实际上 b f s bfs bfs树中除根结点外的 S S S标记点都是已匹配点且它的匹配点为它的父亲结点(标记为 T T T)。
2.2. v v v为未匹配点,则找到了一条起点终点都为未匹配点的增广路。
从这个 T T T开始,将 v v v点的匹配设为 p r e v pre_v prev, p r e v pre_v prev的匹配也改成 v v v,再向上跳到原本 m a t c h p r e v match_{pre_v} matchprev的位置( p r e v pre_v prev的父亲结点),不断如此操作直到根。 -
v
v
v有标记
S
S
S,则找到了一个奇环,需要进行缩点。如将下图中的奇环缩成一个点,且点标记为
S
S
S。从这个大点中的任意一个向外增广都是合法的。所以找到
l
c
a
(
x
,
v
)
lca(x,v)
lca(x,v)后遍历这个环,将环中标记为
T
T
T的点改成标记为
S
S
S并压入队列进行增广。

那么此时若增广路通过回溯到某个在奇环上的点 i i i,则表示 i i i在环上的两条邻边不能选,而将其它不相交边依次选中。
注意到如果 i i i点原本就标记为 S S S(它的邻边原本就都不选),则不需要改变。
但是如果 i i i点原本标记为 T T T,回溯时跳原本的 m a t c h i match_i matchi会向下跳到一个原本就标记为 S S S的点,那么就需要将环上所有原本就标记为 S S S的点的 p r e pre pre设成其儿子结点(具体来说就是在跳 l c a lca lca时记录每个标记为 T T T的点 u u u,将 p r e p r e u pre_{pre_u} prepreu赋值为 u u u)。而对于最底层的标记为 S S S点(如图中的 u u u),将其 p r e pre pre设成这条非树边的另一端即可。
p.s.每次增广成功就直接返回了,所以下一次增广前记得清空 q u e u e queue queue。
bzoj4405
将每个筐子拆成三个点。三个点互相连边(均为无向边)。
球可以放进某个箱子就是将球对应的点向对应的筐的三个连边。
答案即为最大匹配数-n。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=605,M=1e6+10;
int tk,n,m,e,lim,bs1,bs2,bs3,ans;
int head[N],to[M],nxt[M],tot;
int pre[N],bel[N],f[N],typ[N],vs[N],tim;
inline void lk(int u,int v)
{
to[++tot]=v;nxt[tot]=head[u];head[u]=tot;
to[++tot]=u;nxt[tot]=head[v];head[v]=tot;
}
int getfa(int x){return x==f[x]?x:(f[x]=getfa(f[x]));}
inline int lca(int x,int y)
{
for(tim++;;swap(x,y)) if(x){
x=getfa(x);
if(vs[x]==tim) return x;
vs[x]=tim;x=pre[bel[x]];
}
}
queue<int>que;
inline void trs(int x,int y,int z)
{
for(;getfa(x)!=z;x=pre[y]){
pre[x]=y;y=bel[x];
if(typ[y]==2) typ[y]=1,que.push(y);
if(getfa(x)==x) f[x]=z;if(getfa(y)==y) f[y]=z;
}
}
inline int ext(int s)
{
int i,j,x,y,lst,z;
for(i=1;i<=lim;++i) f[i]=i;for(;que.size();que.pop());
memset(typ,0,sizeof(typ));memset(pre,0,sizeof(pre));
for(typ[s]=1,que.push(s);que.size();){
x=que.front();que.pop();
for(i=head[x];i;i=nxt[i]){
j=to[i];if(getfa(j)==getfa(x) || typ[j]==2) continue;
if(!typ[j]){
typ[j]=2;pre[j]=x;
if(!bel[j]){
for(x=j;x;x=lst){
y=pre[x];lst=bel[y];bel[y]=x;bel[x]=y;
}
return 1;
}
typ[bel[j]]=1;que.push(bel[j]);
}else{z=lca(x,j);trs(x,j,z);trs(j,x,z);}
}
}
return 0;
}
inline void sol()
{
int i,j,x,y;
memset(head,0,sizeof(head));tot=0;
memset(bel,0,sizeof(bel));
scanf("%d%d%d",&n,&m,&e);
bs1=n;bs2=n+m;bs3=n+m+m;lim=n+m+m+m;
for(i=1;i<=m;++i){lk(bs1+i,bs2+i);lk(bs2+i,bs3+i);lk(bs1+i,bs3+i);}
for(i=1;i<=e;++i){
scanf("%d%d",&x,&y);
lk(x,bs1+y);lk(x,bs2+y);lk(x,bs3+y);
}
ans=0;
for(i=1;i<=lim;++i) ans+=((!bel[i])&&(ext(i)));
printf("%d\n",ans-n);
}
int main(){
for(scanf("%d",&tk);tk;--tk) sol();
return 0;
}

本文介绍了带花树算法用于求解一般图的最大匹配问题,重点讲解了如何处理奇环以及如何通过BFS进行增广路径搜索。通过将奇环缩点并利用并查集维护,将问题转化为二分图匹配。文章还详细阐述了BFS树的构造过程及增广路径的更新策略,并给出了bzoj4405问题的具体应用。
1991

被折叠的 条评论
为什么被折叠?



