日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

图论 —— 带花树算法

發布時間:2025/3/17 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 图论 —— 带花树算法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

【概述】

帶花樹算法用于解決一般圖的最大匹配問題。

對于一個圖 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):點擊這里

總結

以上是生活随笔為你收集整理的图论 —— 带花树算法的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。