一般图最大匹配(带花树)

带花树

Hungary算法的核心就是找增广路。
但是在一般图上,因为有奇环,所以找增广路的时候可能会绕一个奇环一圈然后多次经过同一条边,所以不可以直接Hungary。
注意到对于一个奇环,一定有至少一个点能跟环外匹配,所以我们可以考虑把奇环缩成一个点(开花)。
考虑bfs,并对节点黑白染色。假设起点是黑点,那么队列中只保存黑点。
很显然黑->白的边是非匹配边,白->黑的边是匹配边。
我们遍历当前队首黑点\(u\)的出点\(v\),如果\(v\)未被染色,就将其染成白色。
如果\(v\)已经匹配了,那么将其匹配点染成黑色并加入队列。
否则我们就找到了一条增广路。
\(v\)是白点,那么我们找到了一个偶环,这对匹配没有任何影响,可以直接忽略。
\(v\)是黑点,那么我们找到了一个奇环,在bfs树上找到这两个点的lca,然后开花。
因为奇环开花后是一个黑点,所以环上的白点需要被改成黑点并加入队列。
我们一般用并查集来记录每个点在哪个花中。
同时为了能够沿着增广路走回去,我们需要记录一个\(pre\)表示这个点的前驱。
花上的非匹配边的\(pre\)是双向的,有点像双向链表,缩环时需要处理。

#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<numeric>
#include<algorithm>
const int N=507;
int n,fa[N],col[N],mat[N],pre[N];std::vector<int>e[N];std::queue<int>q;
int read(){int x;scanf("%d",&x);return x;}
int find(int x){return x==fa[x]? x:fa[x]=find(fa[x]);}
int lca(int u,int v)
{
    static int f[N],t;++t;
    for(;f[u]^t;std::swap(u,v)) if(u) f[u]=t,u=find(pre[mat[u]]);
    return u;
}
void blossum(int u,int v,int p){for(;find(u)^p;u=pre[v]) if(pre[u]=v,v=mat[u],fa[u]=fa[v]=p,col[v]==1) col[v]=2,q.push(v);}
int bfs(int s)
{
    while(!q.empty()) q.pop();
    memset(col+1,0,n<<2),std::iota(fa+1,fa+n+1,1),q.push(s),col[s]=2;
    while(!q.empty())
    {
	int u=q.front(),p;q.pop();
	for(int v:e[u])
	    if(!col[v])
	    {
		col[v]=1,pre[v]=u,col[mat[v]]=2,q.push(mat[v]);
		if(!mat[v])
		{
		    while(u) u=mat[pre[v]],mat[v]=pre[v],mat[pre[v]]=v,v=u;
		    return 1;
		}
	    }
	    else if(col[v]==2) p=lca(u,v),blossum(u,v,p),blossum(v,u,p);
    }
    return 0;
} 
int main()
{
    n=read();int m=read(),ans=0;
    for(int i=1,u,v;i<=m;++i) u=read(),v=read(),e[u].push_back(v),e[v].push_back(u);
    for(int i=1;i<=n;++i) if(!mat[i]&&bfs(i)) ++ans;
    printf("%d\n",ans);
    for(int i=1;i<=n;++i) printf("%d ",mat[i]);
}

猜你喜欢

转载自www.cnblogs.com/cjoierShiina-Mashiro/p/12717850.html