之前一直以为一般图的最大匹配是一个NP问题,无意中了解到花树算法才知道一般图最大匹配也有多项式时间算法。用网上的资料学了一波记录一下。
推荐博客:https://www.cnblogs.com/BAJimH/p/10569418.html (这个大佬原理讲得很清楚,代码也有解析)
https://www.cnblogs.com/owenyu/p/6858508.html (这个大佬说得简单一些,代码很好)
看了上面大佬的解释应该就不难理解这个算法了,大佬有句话说得很到位:其实带花树就是匈牙利+处理奇环。为什么?因为其实一般图匹配跟二分图匹配的不用之处在于,一般图可能存在奇环(二分图当然没有),而二分图匹配的经典算法匈牙利算法是不能处理奇环的,所以要特殊处理奇环。我们观察到其实长度为2k+1的奇环内部自己可以形成k对匹配,然后就会剩下也只会剩下一个点向外边寻求匹配,那么从这个角度看奇环可以看作是一个点,所以我们可以把奇环缩点,当这个奇环多出的那个点需要向外寻求匹配的时候再展开奇环,缩完奇环的图就可以当作二分图匹配来跑了。算法重点是怎么缩奇环以及展开奇环,这里用到一种十分巧妙的办法:并查集+LCA。并查集记录每个点所在的奇环的顶点,初始时就是它自己。缩环的时候,我们直接将环上的所有点并查集父亲连向奇环的顶点,并将环上点染色,并且加入队列。找到一个新的奇环,那么找到u,v所在奇环的环顶(即它们在BFS上跑出来的交错树的lca,称之为最近公共花祖先),将u到环顶的路径以及v到环顶的路径修改掉,白点染成黑点,加入队列,并将环上的点(或者是某个已经缩了的环顶)并查集父亲指向lca。(这段话是大佬原话,建议直接去大佬博客看)
惭愧的是,虽然本蒟蒻是能看能原理,但是其实也不能深入地了解代码实现细节,所以导致不看代码自己写不出来,暂且先把它当成黑盒代码来用吧。
题目练习:
UOJ #79
带花树匹配裸题,代码参考大佬的。输入一张n点m条边的图,输出最大匹配以及一种最大匹配可行方案。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=505; 4 const int M=N*N*2; 5 int n,m,que[M],ql,qr,pre[N],tim=0; 6 int h[N],tot=0; 7 int match[N],f[N],tp[N],tic[N]; 8 struct edge { 9 int v,nxt; 10 } e[M]; 11 12 int find(int x) { return f[x]==x?f[x]:f[x]=find(f[x]); } 13 14 void add(int u,int v) { 15 e[++tot]=(edge){v,h[u]}; 16 h[u]=tot; 17 } 18 19 int lca(int x,int y) { 20 for (++tim;;swap(x,y)) if (x) { 21 x=find(x); 22 if (tic[x]==tim) return x; else tic[x]=tim,x=pre[match[x]]; 23 } 24 } 25 26 void shrink(int x,int y,int p) { 27 while (find(x)!=p) { 28 pre[x]=y,y=match[x]; 29 if (tp[y]==2) tp[y]=1,que[++qr]=y; 30 if (find(x)==x) f[x]=p; 31 if (find(y)==y) f[y]=p; 32 x=pre[y]; 33 } 34 } 35 36 bool aug(int s) { 37 for (int i=1;i<=n;++i) f[i]=i; 38 memset(tp,0,sizeof tp),memset(pre,0,sizeof pre); 39 tp[que[ql=qr=1]=s]=1; // 1: type A ; 2: type B 40 int t=0; 41 while (ql<=qr) { 42 int x=que[ql++]; 43 for (int i=h[x],v=e[i].v;i;i=e[i].nxt,v=e[i].v) { 44 if (find(v)==find(x) || tp[v]==2) continue; 45 if (!tp[v]) { 46 tp[v]=2,pre[v]=x; 47 if (!match[v]) { 48 for (int now=v,last,tmp;now;now=last) { 49 last=match[tmp=pre[now]]; 50 match[now]=tmp,match[tmp]=now; 51 } 52 return true; 53 } 54 tp[match[v]]=1,que[++qr]=match[v]; 55 } else if (tp[v]==1) { 56 int l=lca(x,v); 57 shrink(x,v,l); 58 shrink(v,x,l); 59 } 60 } 61 } 62 return false; 63 } 64 65 int main() 66 { 67 scanf("%d%d",&n,&m); 68 for (int i=1;i<=m;++i) { 69 int x,y; scanf("%d%d",&x,&y); 70 add(x,y),add(y,x); 71 } 72 int ans=0; 73 for (int i=1;i<=n;++i) ans+=(!match[i] && aug(i)); 74 printf("%d ",ans); 75 for (int i=1;i<=n;++i) printf("%d ",match[i]); 76 puts(""); 77 return 0; 78 }
ZOJ 3316
题意给出二维棋盘的n个棋子,给出一个距离L。有两个人轮流把棋盘上的棋子拿开,只有当前想拿的棋子与上一次对方拿走的棋子的曼哈顿距离小于等于L才可以拿走,双方都足够理智,问后手是否必胜。这道题咋一看是博弈论,但是这个并不是公平游戏,所以也不能用博弈论的知识SG函数等等解决。容易想到把图上两个距离小于等于L的棋子连边,这幅图就变成多个联通块,我们先只考虑一个联通块什么情况下先手必胜/后手必胜?自己手画几个图发现只要这个联通块不是完美匹配,先手就有必胜的策略(这也是博主提前知道这道题是用带花树解决才能想到qwq)。反之只有联通块是完美匹配后手才必胜。那么有多个联通块怎么办?这也比较容易想,先手想必胜肯定选一个能让他必胜的联通块,所有只要存在至少一个非完美匹配的联通块就是先手必胜。所以带花树一下即可。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=370; 4 const int M=N*N*2; 5 int n,m,x[N],y[N],L,que[M],ql,qr,pre[N],tim=0,scc,id[N],siz[N],num[N]; 6 int h[N],tot=0; 7 int match[N],f[N],tp[N],tic[N]; 8 struct edge { 9 int v,nxt; 10 } e[M]; 11 12 int find(int x) { return f[x]==x?f[x]:f[x]=find(f[x]); } 13 14 void add(int u,int v) { 15 e[++tot]=(edge){v,h[u]}; 16 h[u]=tot; 17 } 18 19 int lca(int x,int y) { 20 for (++tim;;swap(x,y)) if (x) { 21 x=find(x); 22 if (tic[x]==tim) return x; else tic[x]=tim,x=pre[match[x]]; 23 } 24 } 25 26 void shrink(int x,int y,int p) { 27 while (find(x)!=p) { 28 pre[x]=y,y=match[x]; 29 if (tp[y]==2) tp[y]=1,que[++qr]=y; 30 if (find(x)==x) f[x]=p; 31 if (find(y)==y) f[y]=p; 32 x=pre[y]; 33 } 34 } 35 36 bool aug(int s) { 37 for (int i=1;i<=n;++i) f[i]=i; 38 memset(tp,0,sizeof tp),memset(pre,0,sizeof pre); 39 tp[que[ql=qr=1]=s]=1; // 1: type A ; 2: type B 40 int t=0; 41 while (ql<=qr) { 42 int x=que[ql++]; 43 for (int i=h[x],v=e[i].v;i;i=e[i].nxt,v=e[i].v) { 44 if (find(v)==find(x) || tp[v]==2) continue; 45 if (!tp[v]) { 46 tp[v]=2,pre[v]=x; 47 if (!match[v]) { 48 for (int now=v,last,tmp;now;now=last) { 49 last=match[tmp=pre[now]]; 50 match[now]=tmp,match[tmp]=now; 51 } 52 return true; 53 } 54 tp[match[v]]=1,que[++qr]=match[v]; 55 } else if (tp[v]==1) { 56 int l=lca(x,v); 57 shrink(x,v,l); 58 shrink(v,x,l); 59 } 60 } 61 } 62 return false; 63 } 64 65 void dfs(int x) { 66 id[x]=scc; siz[scc]++; 67 for (int i=h[x];i;i=e[i].nxt) { 68 int y=e[i].v; 69 if (!id[y]) dfs(y); 70 } 71 } 72 73 int main() 74 { 75 while (scanf("%d",&n)!=EOF) { 76 for (int i=1;i<=n;i++) scanf("%d%d",&x[i],&y[i]); 77 scanf("%d",&L); 78 tim=scc=tot=0; 79 for (int i=1;i<=n;i++) h[i]=match[i]=tp[i]=f[i]=tic[i]=pre[i]=0; 80 for (int i=1;i<=n;i++) 81 for (int j=i+1;j<=n;j++) 82 if (abs(x[i]-x[j])+abs(y[i]-y[j])<=L) add(i,j),add(j,i); 83 84 for (int i=1;i<=n;i++) siz[i]=num[i]=id[i]=0; 85 for (int i=1;i<=n;i++) 86 if (!id[i]) scc++,dfs(i); 87 88 for (int i=1;i<=n;++i) num[id[i]]+=(!match[i] && aug(i)); 89 90 bool ok=1; 91 for (int i=1;i<=scc;i++) 92 if (num[i]*2!=siz[i]) ok=0; 93 printf("%s ",ok?"YES":"NO"); 94 } 95 96 return 0; 97 }
BZOJ 4405 / Luogu 4258
n个球m个框。每个球要放在一个框里,每个框最多只能放3个球。定义一个框内的球数<=1为半空,问最佳匹配方案能使半空的框最多。这道题非常妙,蒟蒻博主也日常不会做,题解参考https://blog.csdn.net/qq_30974369/article/details/79822551这位大佬的。
这位大佬分析得很好了。建图方式是:每个篮子拆成三个点,点与点之间互相连边。每个球是一个点,可以向它可以放的篮子拆出来的三个点连边。这样建图后跑一般图最大匹配结果在保证所有球都有匹配的情况下, 最大匹配数=球数+有贡献的篮子自身的匹配。那么此题就解决了。
洛谷的此题还要求输出合理方案吗,在带花树匹配过程中就会保存方案。那么只要注意匹配过程中要保证先处理球与篮子的匹配,再处理篮子与篮子的匹配,这样才能保证在球都有匹配情况下的最大贡献(在博主的程序中的体现就是主程序的for循环先循环球的匹配在处理篮子的匹配)。否则求出来的贡献是对的,但是方案数不对。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=605; 4 const int M=N*N*2; 5 int n,n1,n2,m,que[M],ql,qr,pre[N],tim=0; 6 int h[N],tot=0; 7 int match[N],f[N],tp[N],tic[N]; 8 struct edge { 9 int v,nxt; 10 } e[M]; 11 12 int find(int x) { return f[x]==x?f[x]:f[x]=find(f[x]); } 13 14 void add(int u,int v) { 15 e[++tot]=(edge){v,h[u]}; 16 h[u]=tot; 17 } 18 19 int lca(int x,int y) { 20 for (++tim;;swap(x,y)) if (x) { 21 x=find(x); 22 if (tic[x]==tim) return x; else tic[x]=tim,x=pre[match[x]]; 23 } 24 } 25 26 void shrink(int x,int y,int p) { 27 while (find(x)!=p) { 28 pre[x]=y,y=match[x]; 29 if (tp[y]==2) tp[y]=1,que[++qr]=y; 30 if (find(x)==x) f[x]=p; 31 if (find(y)==y) f[y]=p; 32 x=pre[y]; 33 } 34 } 35 36 bool aug(int s) { 37 for (int i=1;i<=n;++i) f[i]=i; 38 memset(tp,0,sizeof tp),memset(pre,0,sizeof pre); 39 tp[que[ql=qr=1]=s]=1; // 1: type A ; 2: type B 40 int t=0; 41 while (ql<=qr) { 42 int x=que[ql++]; 43 for (int i=h[x],v=e[i].v;i;i=e[i].nxt,v=e[i].v) { 44 if (find(v)==find(x) || tp[v]==2) continue; 45 if (!tp[v]) { 46 tp[v]=2,pre[v]=x; 47 if (!match[v]) { 48 for (int now=v,last,tmp;now;now=last) { 49 last=match[tmp=pre[now]]; 50 match[now]=tmp,match[tmp]=now; 51 } 52 return true; 53 } 54 tp[match[v]]=1,que[++qr]=match[v]; 55 } else if (tp[v]==1) { 56 int l=lca(x,v); 57 shrink(x,v,l); 58 shrink(v,x,l); 59 } 60 } 61 } 62 return false; 63 } 64 65 int id(int y,int t) { return n1+(y-1)*3+t; } 66 67 int main() 68 { 69 int T; cin>>T; 70 while (T--) { 71 scanf("%d%d%d",&n1,&n2,&m); 72 n=n1+n2*3; 73 tim=tot=0; 74 for (int i=1;i<=n;i++) h[i]=match[i]=tp[i]=f[i]=tic[i]=pre[i]=0; 75 for (int i=1;i<=m;++i) { 76 int x,y; scanf("%d%d",&x,&y); 77 add(x,id(y,1)),add(id(y,1),x); 78 add(x,id(y,2)),add(id(y,2),x); 79 add(x,id(y,3)),add(id(y,3),x); 80 } 81 for (int i=1;i<=n2;i++) { 82 add(id(i,1),id(i,2)),add(id(i,2),id(i,1)); 83 add(id(i,1),id(i,3)),add(id(i,3),id(i,1)); 84 add(id(i,2),id(i,3)),add(id(i,3),id(i,2)); 85 } 86 int ans=0; 87 for (int i=1;i<=n;++i) ans+=(!match[i] && aug(i)); 88 printf("%d ",ans-n1); 89 //for (int i=1;i<=n1;++i) printf("%d ",(match[i]-n1-1)/3+1); 90 //puts(""); 91 } 92 return 0; 93 }