带花树

· · 个人记录

2022.12.15。

解决一般图最大匹配问题。

一般图存在奇环,而奇环可以直接卡掉匈牙利算法,所以需要一些改进。

对于一个奇环,点数为 2k+1,那么它的内部最多有 k 组匹配,并且会有一个点向外匹配,如果将内部匹配忽略,就可以直接看成一个点,因为不管是哪个点向外匹配都可以调整内部匹配使得产生总共 k+1 组匹配。所以可以直接 把奇环缩成一个点 来处理。
使用并查集维护奇环的根(最高点),如果将 matchpre 看作边(匹配边是 match,连接两组匹配的边是 pre),就会获得一棵树。新出现一个奇环时,求出 u,v\text{LCA}(注意这部分可以暴力求,因为每次求完之后就会缩点,路径上的点只会被访问一次,总复杂度 O(n)),然后分别进行 \textbf{Blossom(u,v,lca)}\textbf{Blossom(v,u,lca)} 操作。

```cpp inline int LCA(int a,int b){ static int Dfn=0; ++Dfn,a=Find(a),b=Find(b); while(dfn[a]!=Dfn){ dfn[a]=Dfn,a=Find(pre[mat[a]]); if(b) swap(a,b); } return a; } ``` $\textbf{Blossom}$: ```cpp inline void Blossom(int a,int b,int lca){ while(Find(a)!=lca){ pre[a]=b,b=mat[a]; if(col[b]==2) col[b]=1,q[++r]=b; if(Find(a)==a) Fa[a]=lca; if(Find(b)==b) Fa[b]=lca; a=pre[mat[a]]; } } ``` $\textbf{Match}$($\text{BFS}$): ```cpp inline bool Match(int S){ auto Rec = [](int u,int lst=0){ while(u) lst=mat[pre[u]],mat[u]=pre[u],mat[pre[u]]=u,u=lst; }; for(int i=1;i<=n;++i) col[i]=pre[i]=0,Fa[i]=i; q[l=r=col[S]=1]=S; while(l<=r){ int u=q[l++]; gfore(u) if(Find(u)!=Find(v=e[i].to)&&col[v]!=2){ if(!col[v]){ col[v]=2,pre[v]=u; if(!mat[v]) return Rec(v),1; col[mat[v]]=1,q[++r]=mat[v]; } else{ int lca = LCA(u,v); Blossom(u,v,lca),Blossom(v,u,lca); } } } return 0; } ``` 必须 **注意黑白点往前跳的方式**,`Rec()` 中是白点开始不停跳白点,所以是 `mat[pre[u]]`,而 `LCA()` 和 `Blossom()` 都是黑点开始的所以用 `pre[mat[u]]`。 感觉复杂度是 $O(n^3)$ 左右(?,~~但是从速度上看起来很网络流啊。~~ *** - ZOJ3316 Qry:有一个 $19 \times 19$ 的棋盘,$n$ 个棋子,两人轮流取,每次取的棋子和上一个人取的棋子的曼哈顿距离 $\le L$,均以最优策略进行,判断后手能否获胜。$n \le 361$。 Sol:直接 $n^2$ 连边,获得一张一般图,显然当存在完美匹配的时候后手必胜,只要顺着匹配选就行了,反之先手必胜。直接做就行了。 - HDU3551 Qry:给出一张无向图,可能有重边,无自环,给出一个序列 $\{a_n\}$,求任意一张生成子图满足点 $i$ 的度数是 $a_i$。$n \le 50,m \le 100$。 Sol:对每条边拆成两个点,分别向两个端点拆成的度数点连边,求出完美匹配后就是方案。 放个完整代码: ```cpp const int N = 3003; int n,m,tot,a[53],pre[N],col[N],mat[N],q[N],l,r,Fa[N],dfn[N]; vector<int> e[N],nd[53]; vector<pair<int,int> > E; inline void add(int u,int v){ e[u].emplace_back(v); } inline void Add(int u,int v){ add(u,v),add(v,u); } inline int Find(int x){ return x==Fa[x]?x:Fa[x]=Find(Fa[x]); } inline int LCA(int a,int b){ static int Dfn = 0; ++Dfn,a=Find(a),b=Find(b); while(dfn[a]^Dfn) dfn[a]=Dfn,a=Find(pre[mat[a]]),b&&(a^=b^=a^=b); return a; } inline void Blossom(int a,int b,int lca){ while(Find(a)^lca){ pre[a]=b,b=mat[a]; if(col[b]==2) col[b]=1,q[++r]=b; if(Find(a)==a) Fa[a]=lca; if(Find(b)==b) Fa[b]=lca; a=pre[mat[a]]; } } inline bool Match(int x){ auto R = [](int u,int lst=0){ while(u) lst=mat[pre[u]],mat[u]=pre[u],mat[pre[u]]=u,u=lst; }; for(int i=1;i<=tot;++i) col[i]=pre[i]=0,Fa[i]=i; q[l=r=col[x]=1]=x; while(l<=r){ int u=q[l++]; for(int v:e[u]) if(Find(u)!=Find(v)&&col[v]!=2){ if(!col[v]){ col[v]=2,pre[v]=u; if(!mat[v]) return R(v),1; col[mat[v]]=1,q[++r]=mat[v]; } else{ int lca = LCA(u,v); Blossom(u,v,lca),Blossom(v,u,lca); } } } return 0; } main(){ int T; rd(T); for(int R=1;R<=T;++R){ rd(n),rd(m),tot=m<<1,E.clear(); int res=0; for(int i=1;i<=n;++i) nd[i].clear(); for(int i=1,u,v;i<=m;++i) rd(u),rd(v),E.emplace_back(u,v); for(int i=1;i<=n;++i){ rd(a[i]); for(int j=1;j<=a[i];++j) nd[i].emplace_back(++tot); } for(int i=1;i<=tot;++i) e[i].clear(),mat[i]=0; for(int i=0;i<m;++i){ Add((i<<1)+1,(i<<1)+2); int u=E[i].first,v=E[i].second; for(int x:nd[u]) Add((i<<1)+1,x); for(int x:nd[v]) Add((i<<1)+2,x); } for(int i=1;i<=tot;++i) if(!mat[i]&&Match(i)) ++res; printf("Case %d: ",R),puts((res<<1)>=tot?"YES":"NO"); } } ``` - HDU3446(没写) Qry:给出一张 $n \times m$ 的棋盘,有一些位置 `#` 不能走,其它的 `.` 可以走,有一个王会被放在给定的地方 `K`,每一步可以走的范围如下图: ![](https://cdn.luogu.com.cn/upload/image_hosting/ice5yt87.png) 走过的格子不能再走,现在 Aloce 和 Bib (?)轮流按最优策略操作,不能移动的人就输了,问谁会获得胜利。$n,m \le 15$。 Sol:直接连边跑最大匹配,如果 `K` 一定在最大匹配里则先手必胜。 Upd on 2023.3.30:这个是一般图博弈的结论。