「学习笔记」带花树

「学习笔记」带花树

引子

最大匹配问题, 二分图可以用匈牙利算法, 那一般图呢......?

算法

一 思路

二分图中的匈牙利算法, 是一个 \(O(nm)\) 的算法, 每次寻找增广路的复杂度为 \(O(m)\). 之所以能达到这个复杂度, 是因为在二分图上有一个结论 :

若在寻找以 \(u\) 为起点的增广路时经过了点 \(v\) 并且最终没有找到增广路, 则 \(v\) 一定不在以点 \(u\) 为起点的增广路上.

感性理解一下, 二分图是没有奇环的, 那么从点 \(u\) 到点 \(v\), 不管走哪条路径, 最后到达点 \(v\) 时的那条边要么始终是匹配边, 要么始终是非匹配边.

有了这个结论, 在寻找增广路时, 就可以边找边给每一个点打上标记, 下次不再访问这个点.


到了一般图上, 由于有了奇环的存在, 上面的性质就不成立了. 如下图 (图源)

img

从点 \(1\) 开始找增广路,

若往 \(2\) 方向走, 则会经过 \((1,2),(2,4),(4,5)\), 最后找不到增广路.

若往 \(3\) 方向走, 则会经过 \((1,3),(3,5),(5,4),(4,2),(2,6)\), 最终找到一条增广路.

原因就是往奇环的不同方向走, 最终会由不同的边到达点 \(2\).

因此, 匈牙利算法对于一般图不成立.


虽然奇环使得匈牙利算法无法使用, 但它也有我们可以利用的性质 :

若从奇环上的一点出发找到了增广路, 以奇环上每一个点为起点都存在增广路.

这也不难理解, 画个奇环往不同方向走一遍就好了.


利用这个性质, 我们可以用 \(bfs\) 寻找增广路的时候, 并在寻找的同时把奇环缩成一个点, 这样就避免了 \(dfs\) 寻找增广路的 \(O(nm)\) 复杂度.


二 过程

总算法就是对每个点都找一遍增广路, 那么这里就讲一下找增广路的过程.


为了判断奇环, 我们考虑把节点染色, 设起点为黑色, \(bfs\) 过程中会遇到几种情况. 设当前点为 \(u\), 遇到的点为 \(v\),

  1. \(v\) 没有颜色, 若 \(v\) 没有匹配的话, 就找到了增广路; 若 \(v\) 已被匹配, 则把 \(v\) 染成白色, 把 \(match[v]\) 染成黑色, 并把 \(match[v]\) 放进队列里.
  2. \(v\) 为白色, 则跳过.
  3. \(v\)\(u\) 在一个奇环内, 跳过.
  4. \(v\) 为黑色, 则存在奇环, 找到 \(u,v\) (在 \(bfs\) 路径上) 的 \(lca\), 进行缩点.

(找 \(lca\) 的方法网上大部分都是直接暴力找, 我尝试过倍增, 但在 \(uoj\)\(MLE\), 还不是很清楚是什么问题.)

缩点时, 并不是实际缩点, 只需要维护并查集, 并把 \(pre\) 数组连上就好了.

并且再根据奇环的性质, 把环上的每一个点都染成黑色, 并放进队列里.

代码

UOJ#79 一般图最大匹配

#include<bits/stdc++.h>
using namespace std;
const int _=500+7;
const int __=249500+7;
int n,m,col[_],pre[_],mat[_],fp[_],sym,fa[_],ans;   // col 1: black  2: white
int lst[_],nxt[__],to[__],tot;
queue<int> q;
void add(int x,int y){ nxt[++tot]=lst[x]; to[tot]=y; lst[x]=tot; }
int find(int x){ return fa[x]==x ?x: fa[x]=find(fa[x]); }
int Lca(int x,int y){
  sym++;
  while(1){
    if(x){    // 少了这个条件的话会使得 fp[0]==sym, 导致求得 lca 为 0, 导致下面的函数死循环.
      x=find(x);
      if(fp[x]==sym) return x;
      else{ fp[x]=sym; x=pre[mat[x]]; }
    }
    swap(x,y);
  }
  return 0;
}
void con(int x,int y,int lca){
  while(find(x)!=lca){
    pre[x]=y; fa[x]=lca;
    y=mat[x];
    q.push(y); col[y]=1;
    mat[y]=x; fa[y]=lca;
    x=pre[y];
  }
}
bool aug(int x){
  for(int i=1;i<=n;i++){ col[i]=pre[i]=0; fa[i]=i; }
  while(!q.empty()) q.pop();
  q.push(x); col[x]=1;
  while(!q.empty()){
    int u=q.front(); q.pop();
    for(int i=lst[u];i;i=nxt[i]){
      int v=to[i];
      if(find(u)==find(v)||col[v]==2) continue;
      if(!col[v]){
	col[v]=2; pre[v]=u;
	if(!mat[v]){
	  while(v){
	    int t=mat[pre[v]];
	    mat[v]=pre[v];
	    mat[pre[v]]=v;
	    v=t;
	  }
	  return 1;
	}
	else{ col[mat[v]]=1; q.push(mat[v]); }
      }
      else{
	int lca=Lca(u,v);
	con(u,v,lca); con(v,u,lca);
      }
    }
  }
  return 0;
}
int main(){
  cin>>n>>m; int x,y;
  for(int i=1;i<=m;i++){
    scanf("%d%d",&x,&y);
    add(x,y); add(y,x);
  }
  for(int i=1;i<=n;i++)
    if(!mat[i]) ans+=aug(i);
  printf("%d\n",ans);
  for(int i=1;i<=n;i++) printf("%d ",mat[i]); putchar('\n');
  return 0;
}

例题

UOJ#79【模板】一般图最大匹配

[WC2016]挑战NPC

参考资料

带花树 | _rqy's Blog

带花树总结 - 租酥雨

posted @ 2020-01-03 21:56  BruceW  阅读(119)  评论(0编辑  收藏  举报