带花树草解

这个东西其实看看就好, \(n^3\) 次的做法虽说也是多项式但总给人很暴力的感觉...

链接:uoj #79

我们都知道二分图最大匹配可以匈牙利、网络流,因为二分图只有一边连向另一边,换句话说就是不存在奇环(可以简单证明),这是增广路算法可以解决的

对于有向无环图,网络流算法也可以跑得非常优秀,但是对于一般图来说,这两个算法都束手无策了

一般图的背景一般是一堆男生(n 个), 0 个女生,两个男生间存在边,表示某种关♂系,然后求最大匹配数

那么这样的图中是有可能存在奇环的,奇环没有增广路(本来都不知道增广路是个啥,写这篇随笔的时候才去看...),

这时候就要树上开花啦~

我们考虑和匈牙利时一样,每次帮一个点匹配,但是这里用的是广搜的方式(匈牙利是深搜),建出 bfs 树并在上面进行相应的奇环缩点操作(也就是拿出一个点,剩下的两两匹配),因为可以证明奇环上任意一个点被匹配后其他点都可以两两匹配

这里的 match 如同名称表示与谁匹配,并且这里可以将 pre 数组理解为备胎匹配的数组

code

//by Judge
#include<cstdio>
#include<cstring>
#include<iostream>
#define Rg register
#define fp(i,a,b) for(Rg int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(Rg int i=(a),I=(b)-1;i>I;--i)
#define go(G,u) for(Rg int i=G.head[u],v=G.e[i].to;i;v=G.e[i=G.e[i].nxt].to)
#define ll long long
using namespace std;
const int inf=1e9+7;
const int N=503;
const int M=N*N;
typedef int arr[N];
#ifndef Judge
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#endif
char buf[1<<21],*p1=buf,*p2=buf;
inline int read(){ int x=0,f=1; char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f;
} char sr[1<<21],z[20];int CCF=-1,Z;
inline void Ot(){fwrite(sr,1,CCF+1,stdout),CCF=-1;}
inline void print(int x,char chr=' '){
    if(CCF>1<<20)Ot();if(x<0)sr[++CCF]=45,x=-x;
    while(z[++Z]=x%10+48,x/=10);
    while(sr[++CCF]=z[Z],--Z);sr[++CCF]=chr;
} int n,m,tim,ans,h,t,q[M]; arr f,vis,pre,match,ty;
struct Gr{ int pat,head[M];
    struct Edge{int to,nxt;}e[M<<1];
    inline void add(int u,int v){
        e[++pat]={v,head[u]},head[u]=pat;
        e[++pat]={u,head[v]},head[v]=pat;
    }
}G;
int find(int x){return f[x]^x?f[x]=find(f[x]):x;}
inline int LCA(int x,int y){ //两个点轮流跳 fa,直到一个访问过的另一个访问到了 
    for(++tim;;swap(x,y)) if(x){ x=find(x);
        if(vis[x]==tim) return x;
        vis[x]=tim,x=pre[match[x]];
    }
}
inline void shrink(int x,int y,int k){
    while(find(x)^k){
        pre[x]=y,y=match[x]; // 匹配的两个点的 fa 都设为 lca 
        if(ty[y]==2) ty[y]=1,q[++t]=y; //其余点的 type 都变为 1 ,表示是 S 类点 
        if(find(x)==x) f[x]=k;
        if(find(y)==y) f[y]=k;
        x=pre[y];
    }
}
bool path(int S){ q[h=t=1]=S; //去给 S 找匹配(类似匈牙利拆东墙补西墙)
    fp(i,1,n) ty[i]=pre[i]=0,f[i]=i; //清空 pre 
    for(int u;h<=t;++h){ u=q[h];
        go(G,u) if(find(u)^find(v)&&ty[v]^2) //除去缩成一个点的情况以及已经是 T 类点 
            if(!ty[v]){ //没有访问过的情况 
                pre[v]=u,ty[v]=2;
                if(!match[v]){ //当前点没有被匹配的话目的就已经达到了,直接修改树上一条链的匹配关系 
                    int tmp;
                    do{ tmp=v,
                        match[v]=u,v=match[u],
                        match[u]=tmp,u=pre[v];
                    }while(u); //修改链上的匹配关系 
                    return 1;
                } q[++t]=match[v],ty[match[v]]=1; //当前点匹配了,就连同匹配点一起丢到队列中  
            } else{ //形成奇环,开始缩点 
                int lca=LCA(u,v); //找到 bfs 树上的 lca 
                shrink(u,v,lca),shrink(v,u,lca); //缩点 
            }
    } return 0;
}
int main(){ n=read(),m=read(); int x,y; //主函数都比较朴素 
    fp(i,1,m) x=read(),y=read(),G.add(x,y);
    fp(i,1,n) ans+=(!match[i]&&path(i));
    print(ans,'\n'); fp(i,1,n) print(match[i]);
    return Ot(),0;
}

猜你喜欢

转载自www.cnblogs.com/Judge/p/10630190.html