图论 —— 带花树算法
【概述】
帶花樹算法用于解決一般圖的最大匹配問題。
對于一個圖 G(V,E),他的匹配 M 是二元組 (u,v) 組成的集合,其中 u,v∈V,(u,b)∈E,且 M 中不存在重復的點,當 |M| 最大的時候,稱 M 為圖 G(V,E) 的最大匹配。
當圖 G(V,E) 是一個二分圖時,由于其若含有環,則一定是偶環(一個點數為 2k 的環),其最大匹配可以使用匈牙利算法或網絡流算法來求解。
當圖 G(V,E) 是一個一般圖時,由于一般圖會含有奇環(一個點數為 2k+1 的環),而經過一個奇環會得到兩條含有同一個點的匹配邊,因此當圖 G(V,E) 是一個一般圖時,無法直接進行增廣,需要用改進算法來求解最大匹配,即帶花樹算法。
【帶花樹算法】
帶花樹算法仍是分 n 個階段尋找增廣路,由于問題出在奇環上,那么首先分析一下奇環的性質。
奇環中有 2k+1 個點,所以最多有 k 組匹配,也就是說有一個點沒有匹配,即這個點在環內兩邊的連邊都不是匹配邊,也只有這個點可以向環外連邊。
根據這個性質,可以將奇環縮成一個點(這個點稱為花),由于增廣路經過奇環,那么奇環內的增廣路可以還原出來,因此縮完點后的圖如果可以找到一條增廣路,那么原圖中也可以找到一條增廣路。
根據上述思想,整個求解過程可以分成 n 個階段,每個階段從沒有匹配的 S?點開始 BFS?尋找增廣路。
搜索的開始,將 S 點加入隊列中,標記為 A 類點,如果從 x 點出發,搜索到一個未標記點,那么有兩種情況:
1)如果這個未標記點 x 有匹配:將這個點設為?B 類點,它的匹配點設為 A 類點,加入隊列繼續增廣。
2)如果這個未標記點 x 沒有匹配:由于是從一個未匹配點開始進行搜索的,所以這說明找到了一條增廣路,沿著過來的邊找回去,展開帶花樹,在搜索的過程中,如果遇到了環,又有兩種情況:
? ? ①修改搜索的過程中,如果遇到偶環:那么由于其不影響求解,因此不用管它
? ? ②修改搜索的過程中,如果遇到奇環:那么找到當前點?x 和找到的點 y,求出他們最近公共花祖先,然后用并查集縮掉環。
在縮環的時候,維護一個 pre 數組,表示回跳的時走到這里該往哪一個方向走回去。回跳的時候,每次找到 pre,然后修改這條邊,接著跳到 pre 原來的 match 處。
如果倒著進入一個花的時候,上方的邊為非匹配邊,那么我們會往下走,這個時候 pre 就應該往下設,中間相遇的位置 pre 互相連接,即:pre[x]=y,pre[y]=x
時間復雜度:由于算法分為 n 個階段,每個階段最多把整個圖遍歷一次,每個點會最多被縮 n 次花,所以總復雜度為 O(n3)
【模版】
struct Edge {int to,next; } edge[N*N*2]; int head[N],tot; int n;//n個點 int father[N],pre[N];//father記錄一個點屬于哪個一個點為根的花 int Q[N*N*2],first,tail;//bfs隊列 int match[N];//匹配 bool odd[N],vis[N];//odd記錄一個點為奇點/偶點,1為奇,0為偶 int timeBlock;//LCA時間戳 int top[N],rinedge[N];void addEdge(int x,int y) {//添邊edge[tot].to=y;edge[tot].next=head[x];head[x]=tot++; } int Find(int x){//并查集尋找根節點if(father[x]!=x)return father[x]=Find(father[x]);return x; } int lca(int x, int y){//求解最近公共祖先timeBlock++;while(x){rinedge[x]=timeBlock;x=Find(top[x]);}x=y;while(rinedge[x]!=timeBlock)x=Find(top[x]);return x; } void blossom(int x, int y, int k) {//將奇環縮成一個點并將原來是奇點的點變為偶點并加入隊列while(Find(x)!=Find(k)){pre[x]=y;y=match[x];odd[y]=false;Q[tail++]=y;father[Find(x)]=k;father[Find(y)]=k;x=pre[y];} } bool bfs(int s) {memset(top,0,sizeof(top));memset(pre,0,sizeof(pre));memset(odd,false,sizeof(odd));memset(vis,false,sizeof(vis));for(int i=1;i<=n;i++)father[i]=i;vis[s]=true;first=tail=0;Q[tail++]=s;while(first!=tail){int now=Q[first++];for(int i=head[now];i!=-1;i=edge[i].next){int to=edge[i].to;if(!vis[to]){top[to]=now;pre[to]=now;odd[to]=true;vis[to]=true;if(!match[to]){int j=to;while(j){int x=pre[j];int y=match[x];match[j]=x;match[x]=j;j=y;}return true;}vis[match[to]]=true;top[match[to]]=to;Q[tail++]=match[to];}else if(Find(now)!=Find(to) && odd[to]==false) {int k=lca(now,to);blossom(now,to,k);blossom(to,now,k);}}}return false; }int main() {memset(head,-1,sizeof(head));memset(match,0,sizeof(match));tot=0;int m;scanf("%d%d",&n,&m);for(int i=1;i<=m;i++){int x,y;scanf("%d%d",&x,&y);addEdge(x,y);addEdge(y,x);}int res=0;for(int i=1;i<=n;i++)if(!match[i])res+=bfs(i);printf("%d\n",res);for(int i=1;i<=n;i++)printf("%d ",match[i]);printf("\n");return 0; }【例題】
- 一般圖最大匹配(UOJ-79):點擊這里
- 挑戰NPC(洛谷-P4258):點擊這里
總結
以上是生活随笔為你收集整理的图论 —— 带花树算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 满汉全席(洛谷-P4171)
- 下一篇: 图论 —— 着色问题