带花树学习笔记

带花树是一种用于解决一般图最大匹配的算法(不是数据结构)

一般图与二分图最大的不同是一般图中存在奇环,所以不能和二分图一样的随便找到一条交错路就增广。

带花树的做法是把奇环缩起来(开花),让环上的点找到一个增广路之后能沿着正确的方向往回退,除此之外在思路上与匈牙利差不多。

具体看代码的注释

#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
namespace Patchouli{
  const int N=514,M=310000;
  int n,m;
  int head[N],ver[M],next[M],tot,num;
  int match[N],pre[N],vis[N],f[N],time[N];
  queue<int>q;
  inline void add(int x,int y){
    next[++tot]=head[x],head[x]=tot,ver[tot]=y;
    next[++tot]=head[y],head[y]=tot,ver[tot]=x;
  }
  //并查集
  int get(int x){
    return x==f[x]?x:f[x]=get(f[x]);
  }
  inline int read(){
    int a=1,b=0;char t;
    do{t=getchar();if(t=='-')a=-1;}while(t>'9'||t<'0');
    do{b=b*10-'0'+t;t=getchar();}while(t>='0'&&t<='9');
    return a*b;
  }
  //在BFS树上找LCA,这个是最暴力的方法,可以记录一下depth让跳的合理一点(不过不影响最后的复杂度)
  //这个time只是一个不用清空的vis数组
  inline int LCA(int x,int y){
    ++num;
    //轮流随便跳
    //这里的x和y一定是黑点,模拟BFS的过程往回跳
    //因为BFS是一次走两步的,所以lca也一定是一个黑点
    while(true){
      if(x){
	x=get(x);
	if(time[x]==num)return x;
	else time[x]=num,x=pre[match[x]];
      }
      swap(x,y);
    }
  }
  //开花
  //奥妙重重的变换
  //因为BFS是两步两步进行处理的,所以两个点同时跳
  //x一直是原来的黑点,y除了第一个都是原来的白点
  //原来只有白点会记录pre,现在黑点也可以记录pre,只是记录的pre是方向的(BFS树环上的儿子)
  //原来的白点被标记为黑点,也和黑点一样进行扩展
  //如果是被原来的白点扩展出来的增广路,会通过另一个方向进行取反
  inline void blossom(int x,int y,int z){
    while(get(x)^z){
      pre[x]=y;y=match[x];
      if(vis[y]==2)vis[y]=1,q.push(y);
      f[x]=z,f[y]=z;
      x=pre[y];
    }
  }
  //pre表示在BFS树上的祖先
  //match表示匹配的点
  //vis是染色的状态,约定0为无色,1为黑色,2为白色,最开始增广的点是黑点
  //只有黑点会进入队列,枚举出边
  inline int BFS(int s){
    //清空数组
    while(q.size())
      q.pop();
    for(int i=1;i<=n;++i)
      vis[i]=pre[i]=0,f[i]=i;

    //初始点为黑点,加入队列
    q.push(s);vis[s]=1;
    //BFS
    while(q.size()){
      int x=q.front();
      q.pop();
      //枚举x的出边
      for(int i=head[x];i;i=next[i]){
	int y=ver[i];
	//像匈牙利一样,走一条非匹配边走一条匹配边,所以如果这条已经是匹配边就不用走了(不加也不会出问题,因为就算把已经匹配的点加入队列,下一步就走不了了)
	if(match[y]==x)continue;
	//如果两个点被缩在一起了,或是另外一个点已经被标记为白点了(这表示它增广过了),continue
	if(get(x)==get(y)||vis[y]==2)continue;
	//如果这个点没有被染色,那么将它染为白色  
	if(!vis[y]){
	  vis[y]=2;
	  //记录父亲
	  pre[y]=x;
	  //如果y没有匹配,那么就已经可以增广了
	  if(!match[y]){
	    //沿途取反,同匈牙利
	    for(int o=y,last;o;o=last)
	      last=match[pre[o]],match[o]=pre[o],match[pre[o]]=o;
	    return 1;
	  }
	  vis[match[y]]=1,q.push(match[y]);
	}
	else {
	  //否则一定是一个黑点,因为当前点就是一个黑点,所以出现了奇环,进行开花
	  int z=LCA(x,y);
	  blossom(x,y,z);
	  blossom(y,x,z);
	}
      }
    }
    return 0;
  }
  int QAQ(){
    //读入
    n=read(),m=read();
    int ans=0;
    for(int i=1;i<=m;++i)
      add(read(),read());

    //和匈牙利差不多,如果一个点没有匹配就试图给它增广
    for(int i=1;i<=n;++i)
      if(!match[i])
	ans+=BFS(i);

    //输出答案
    printf("%d
",ans);
    for(int i=1;i<=n;++i)
      printf("%d ",match[i]);
    putchar('
');
    return false;
  }
}
int main(){
  return Patchouli::QAQ();
}
原文地址:https://www.cnblogs.com/oiertkj/p/12246520.html