zoukankan      html  css  js  c++  java
  • 一般图的最大匹配—带花树算法

    又开始乱学东西了

    一般图的最大匹配—带花树算法

    简介

    在此之前,请先确保理解 >二分图匹配<

    对于二分图的最大匹配,我们常用的便是找增广路+给点染白/黑色的匈牙利算法。

    而二分图与一般图的区别就是,二分图没有奇环,一般图有。问题也就出在这个奇环上。

    于是带花树算法(Jack Edmonds于1961年发表)就是来解决这个问题的

    解析

    看下图

    当这个具有 (3) 个点的奇环内存在 (1) 对以上的匹配时,就会发生同色点匹配的情况,匈牙利直接 (O(+infty)) 了。

    所以这时我们要在匈牙利的基础上,对前面的匹配进行修改。因此带花树的基本框架是BFS的匈牙利。

    现在想想怎么修改比较好。

    对于一个有 (2k+1) 个点的奇环,其内部最多能有 (k) 组匹配,同时有一个点匹配到环外的点。
    我们可以将奇环缩到一个点,这个点向外匹配。剩下 (2k) 个点两两匹配。这个缩完的点就叫

    怎么缩环为点呢?我们需要明白,出现同色点匹配的情况,是由于我们从某个点开始搜出来的两条路径碰头了。现在我们要找到这个点。记这个点 (lca)最近公共花祖先,也称花根。可以让两个同色节点交替往上跳,第一个重复点就是花根。由于求完花根我们就会缩环 (开花) ,所以暴力上跳的时间复杂度均摊下来并不高。

    缩环时,我们利用一个记录前驱的 (pre) 数组将环连接起来。将所有的白点染黑然后让这些白点进队以进行后面的增广操作。此时整个花中全部变成了黑点,相当于整个花缩成了一个黑点。为了方便修改以及寻找节点在花中的真正祖先(可能会出现套娃的现象),我们可以使用并查集。

    总时间复杂度约为 (O(nmlog n))

    结合代码食用更佳

    code:

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    #define rg register
    const int N=1e4+10,M=5e5+10;
    
    inline int read()
    {
    	rg int x=0,w=1;
    	rg char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    	if(ch=='-') w=-1,ch=getchar();
    	while(ch<='9'&&ch>='0') x=(x<<3)+(x<<1)+(ch-'0'),ch=getchar();
    	return x*w;
    }
    
    int head[N],ver[M],nxt[M],tot=0;
    void add(int x,int y)
    {
    	ver[++tot]=y;
    	nxt[tot]=head[x];
    	head[x]=tot;
    }
    int fa[N];
    int find(int x)//并查集
    {
    	int x_root=x;
    	while(fa[x_root]!=x_root) x_root=fa[x_root];
    	while(x!=x_root)
    	{
    		int tmp=fa[x];
    		fa[x]=x_root; x=tmp;
    	}
    	return x_root;
    }
    int match[N],pre[N];//记录匹配与前驱
    int vis[N],dfn[N],tim=0;
    int n,m,res=0;
    
    int lca(int x,int y)//求花根
    {
      /*dfn是一个标记数组,一边打标即一边上跳,第一个重复点就是花根
    	花根一定是一个黑点,所以可以隔点上跳*/
    	++tim;
    	x=find(x);y=find(y);
    	while(dfn[x]!=tim)
    	{
    		dfn[x]=tim;
    		x=find(pre[match[x]]);
    		if(y) swap(x,y);
    	}
    	return x;
    }
    
    queue<int> q;
    void blossom(int x,int y,int w)//缩奇环(开花)
    {
    	while(find(x)!=w)
    	{
    		pre[x]=y;y=match[x];//
    		if(vis[y]==2) vis[y]=1,q.push(y);//暴力染黑然后进队
    		if(find(x)==x) fa[x]=w;
    		if(find(y)==y) fa[y]=w;
    		x=pre[y];
    	}
    }
    
    bool solve(int S)//带花树
    {
    	for(int i=1;i<=n;i++) fa[i]=i,vis[i]=pre[i]=0;
    	while(q.size()) q.pop();
    
    	q.push(S);
    	vis[S]=1;
    	while(q.size())
    	{
    		int x=q.front();
    		q.pop();
    		for(int i=head[x];i;i=nxt[i])
    		{
    			int y=ver[i];
    
    			if(find(x)==find(y)||vis[y]==2) continue;//y是白点或是同一花中的点
    			if(!vis[y])//y还没有染色
    			{
    				vis[y]=2; pre[y]=x;
    				if(!match[y])//没被匹配
    				{
    					for(int k=y,p;k;k=p)
    						p=match[pre[k]],match[k]=pre[k],match[pre[k]]=k;//修改匹配
    					return 1;
    				}
    				vis[match[y]]=1,q.push(match[y]);//被匹配过了
    			}
    			else //y是黑色点
    			{
    				int w=lca(x,y);
    				blossom(x,y,w);
    				blossom(y,x,w);//缩环两个方向各一次拼起来
    			}
    		}
    	}
    	return 0;
    }
    
    int main()
    {
    	n=read(),m=read();
    	for(int i=1;i<=m;i++)
    	{
    		int x=read(),y=read();
    		add(x,y); add(y,x);
    	}
    
    	for(int i=1;i<=n;i++)
    		if(!match[i]) res+=solve(i);
    	printf("%d
    ",res);
    	for(int i=1;i<=n;i++)
    		printf("%d ",match[i]);
    	return 0;
    }
    
    
  • 相关阅读:
    欧拉代码005
    欧拉计划003
    欧拉计划004
    欧拉计划006
    欧拉计划002
    LINUXS3C2440SJA1000驱动程序笔记
    WPF实现窗体内容分割
    InotifyPropertyChanged接口实现简单数据绑定
    C#的6种常用集合类大比拼
    WPF获取窗体或控件句柄
  • 原文地址:https://www.cnblogs.com/IzayoiMiku/p/14350352.html
Copyright © 2011-2022 走看看