如何用带花树算法做一般图匹配

模板题uoj79

所谓的“花”,就是一个有奇数个点的环。对于这样的环,我们可以把它看作一个点,在环上就是一个点搭配着另一个点,这样一定会有一个点单出来,要和该花以外的点匹配,这个单身狗就是“花根(花托)”。

bfs找增广路。

假设现在有一个点x还没有找到伙伴,我们先给他钦定一个颜色,姑且认为是黑色,然后往前找。
如果下一个点y是一个已经被访问过的白点或者与x在同一个花中的黑点,跳过不处理。
如果这是一个没有被访问过的点,并且它已经有小伙伴了。x想和y组队,于是去和y的小伙伴z商量,看看这个z能不能找其他小伙伴。z说他可以先试一试,于是x和y之间用一个叫做pre的指针连一下,表示如果z找到了其他朋友,x和y可以考虑做朋友。再将z染成黑色,丢进队列里继续bfs找伙伴。
这个时候,我们找到了一个没有被访问过且没有小伙伴的点t,这就很令人高兴了,先用pre将z和t连起来,我们就让z和t做朋友,y和x做朋友……相当于将以t开头的这一串用pre和cp(匹配指针)连起来的点,cp全部消掉,pre全部变成cp。

while(y) {
    int kl=cp[pre[y]];
    cp[y]=pre[y],cp[pre[y]]=y,y=kl;
}

以上的情况和匈牙利算法也差不多,pre和cp交错的路径就是增广路。
现在问题来了,如果x同学找到了一个黑点y,怎么办呢?
如果一个黑点可以找一个黑点,说明这两个黑点在同一朵花中,所以我们要把它们原来所在的花合并到一朵新花中,这就叫做开花。
首先,我们找到x和y的花上LCA,就是它们向着花托方向走第一个能走到的点。
什么是花上LCA呢,因为一张图可能是一环套一环的,交错复杂的。如果出现x在花A里,y在花B里,花A缩成点后又在花C里,花B又在花D里,花C和花D都在花E里,这样E就是x和y的花LCA。
怎么求LCA?倍增?tarjan?花链剖分?
直接往根跳的时候暴力打标记,遇到打了标记的点说明遇到了LCA。我们知道一条增广路是pre和cp交错的,所以我们也应该交错走pre和cp。

int lca(int x,int y) {
    x=find(x),y=find(y),++tim;
    while(vis[x]!=tim) {
        vis[x]=tim,x=find(pre[cp[x]]);//find:每次请走到花托上
        if(y) swap(x,y);
    }
    return x;
}

而开花的过程,就是除了花托与其中一个和花托相连的点(因为x和y是两个黑点相连,所以它们其中有一个是花托,它们之间的pre边是单向的)之外,其他的边上的pre都要连成双向的,这样方便我们在寻找到与该花有交的增广路后进行增广。
如果存在不经过花托的增广路,那么我们不可能走到要让x和y两个黑点相连的地步,所以与该花有交的增广路一定经过花托。
花托是一个黑点,与外部白点匹配,所以与花有交的增广路一定经过花托和另一个白点。
所以我们在开花的时候,遇到白点,就要将其丢进队列里继续增广。

void bloss(int x,int y,int o) {
    while(find(x)!=o) {//花托点不会进入该循环,所以pre就是单向的
        pre[x]=y,y=cp[x];
        if(bj[y]==2) bj[y]=1,q.push(y);//bj:颜色,2是白色1是黑色
        if(x==find(x)) f[x]=o;
        if(y==find(y)) f[y]=o;
        x=pre[y];
    }
}

总代码:

#include<bits/stdc++.h>
using namespace std;
#define RI register int
int read() {
    int q=0;char ch=' ';
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
    return q;
}
const int N=502,M=250000;
int n,m,tot,tim,ans;
int h[N],ne[M],to[M],cp[N],pre[N],f[N],bj[N],vis[N];
//0:未访问 1:黑点 2:白点
queue<int> q;
void add(int x,int y) {to[++tot]=y,ne[tot]=h[x],h[x]=tot;}
int find(int x) {return x==f[x]?x:f[x]=find(f[x]);}
int lca(int x,int y) {//some questions
    ++tim,x=find(x),y=find(y);
    while(vis[x]!=tim) {
        vis[x]=tim,x=find(pre[cp[x]]);//find:每次请走到花托上
        if(y) swap(x,y);
    }
    return x;
}
void bloss(int x,int y,int o) {
    while(find(x)!=o) {
        pre[x]=y,y=cp[x];
        if(bj[y]==2) bj[y]=1,q.push(y);//如果路上遇到白点
        if(find(x)==x) f[x]=o;
        if(find(y)==y) f[y]=o;
        x=pre[y];
    }
}
int bfs(int st) {
    while(!q.empty()) q.pop();
    for(RI i=1;i<=n;++i) bj[i]=pre[i]=0,f[i]=i;
    bj[st]=1,q.push(st);
    while(!q.empty()) {
        int x=q.front();q.pop();
        for(RI i=h[x];i;i=ne[i]) {
            int y=to[i];
            if(find(x)==find(y)||bj[y]==2) continue;
            if(!bj[y]) {
                bj[y]=2,pre[y]=x;
                if(!cp[y]) {
                    while(y) {
                        int kl=cp[pre[y]];
                        cp[y]=pre[y],cp[pre[y]]=y,y=kl;
                    }
                    return 1;
                }
                q.push(cp[y]),bj[cp[y]]=1;
            }
            else {int o=lca(x,y);bloss(x,y,o),bloss(y,x,o);}
        }
    }
    return 0;
}
int main()
{
    int x,y;
    n=read(),m=read();
    for(RI i=1;i<=m;++i) x=read(),y=read(),add(x,y),add(y,x);
    for(RI i=1;i<=n;++i) if(!cp[i]) ans+=bfs(i);
    printf("%d\n",ans);
    for(RI i=1;i<=n;++i) printf("%d ",cp[i]);
    puts("");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/litble/article/details/80865025