帶花樹
前置技能
匈牙利算法(二分圖最大匹配)
為什么要有帶花樹
考慮一下二分圖和一般圖的最大區別(或者說唯一的區別在哪里)。
二分圖沒有奇環(也就是長度為奇數的環),而一般圖是可以有的。
所以匈牙利算法中的尋找增廣路然后路徑取反的方法在一般圖上就不適用了。
主要還是要解決奇環的問題。
我們發現一個奇環里至少有一個點不能匹配,那就干脆把一個奇環縮成一個點(開花)?
在處理到奇環的時候把它縮成一個點,路徑取反的時候再暴力展開一個個取反。
算法流程
我們給所有點黑白染色。假設開始增廣的點是黑點。
把所有黑點壓進隊列中順次處理。對於一個黑點\(u\),找與他相鄰的點\(v\),會出現一下幾種情況:
1、\(u,v\)已經被縮成一個點了(這兩個點在一朵花里),不管他。
2、\(v\)是白點,說明已經被匹配了,也不管。
3、\(v\)還沒有被染色。那就先把這個點染成白的,然后嘗試去與他匹配。如果\(v\)還沒有匹配就匹配上,增廣成功,然后一路跳回取反。如果\(v\)已經被匹配了,那么匹配他的點就是個黑點,染色,然后壓進隊列。
4、\(v\)也是黑點。這時候染色發生了沖突,說明遇見了奇環。這時候就需要找到兩個點的\(lca\),然后把這整個環縮成一個點。美其名曰,開花。
怎么開花
開花的時候大致要做這么幾件事:(摘自無向圖匹配的帶花樹算法)
1。找\(x\)和\(y\)的\(LCA\)(的根)\(p\)。找\(LCA\)可以用各種方法。。。直接朴素也行。
2。在\(pre\)數組中把\(x\)和\(y\)接起來(表示它們形成環了!)
3。從\(x\)、\(y\)分別走到\(p\),修改並查集使得它們都變成一家人,同時沿路把\(pre\)數組接起來。
code
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
int gi()
{
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 505;
int n,m,to[N*N<<1],nxt[N*N<<1],head[N],cnt;
int match[N],pre[N],vis[N],fa[N],tim[N],idx,ans;
queue<int>Q;
int link(int u,int v){to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;}
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
int lca(int x,int y)
{
for (++idx;;swap(x,y))
if (x)
{
x=find(x);
if (tim[x]==idx) return x;
else tim[x]=idx,x=pre[match[x]];
}
}
void blossom(int x,int y,int p)
{
while (find(x)!=p)
{
pre[x]=y;y=match[x];
if (vis[y]==2) vis[y]=1,Q.push(y);
if (find(x)==x) fa[x]=p;
if (find(y)==y) fa[y]=p;
x=pre[y];
}
}
int Aug(int S)
{
for (int i=1;i<=n;++i)
vis[i]=pre[i]=0,fa[i]=i;
while (!Q.empty()) Q.pop();
Q.push(S);vis[S]=1;
while (!Q.empty())
{
int u=Q.front();Q.pop();
for (int e=head[u];e;e=nxt[e])
{
int v=to[e];
if (find(u)==find(v)||vis[v]==2) continue;
if (!vis[v])
{
vis[v]=2;pre[v]=u;
if (!match[v])
{
for (int x=v,lst;x;x=lst)
lst=match[pre[x]],match[x]=pre[x],match[pre[x]]=x;
return 1;
}
vis[match[v]]=1,Q.push(match[v]);
}
else
{
int gg=lca(u,v);
blossom(u,v,gg);blossom(v,u,gg);
}
}
}
return 0;
}
int main()
{
n=gi();m=gi();
for (int i=1;i<=m;++i)
{
int u=gi(),v=gi();
link(u,v);link(v,u);
}
for (int i=1;i<=n;++i) if (!match[i]) ans+=Aug(i);
printf("%d\n",ans);
for (int i=1;i<=n;++i) printf("%d ",match[i]);
puts("");return 0;
}