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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

带花树算法

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

對于一般的二分圖匹配我們肯定會想到匈牙利算法,但是如果圖中出現奇環怎么辦?此時匈牙利算法就不可以了,就需要另一個算法:帶花樹算法
主要就是為了解決奇環的問題
我們匹配時會發現,如果存在奇環,傳統的匈牙利算法在一個奇環里至少有一個點不能匹配,那么干脆就把這個奇環縮成一個點(也叫開花,這就是算法名字的由來),在處理到奇環的時候把它縮成一個點,路徑取反的時候再暴力展開一個個取反。
縮點用并查集來維護

流程:

參考代碼
我們給所有點黑白染色。假設開始增廣的點是黑點。把所有黑點壓進隊列中順次處理。對于一個黑點u,找與他相鄰的點v,會出現一下四種情況:

1、u,v已經被縮成一個點了(這兩個點在一朵花里),跳過。

2、v是白點,說明已經被匹配了,也就是偶環,跳過。

3、v還沒有被染色。那就先把這個點染成白的,然后嘗試與u匹配。如果v還沒有匹配就匹配上,增廣成功,然后沿增廣路取反。如果v已經被匹配了,那么匹配他的點就是個黑點,染色,然后壓進隊列。

4、v也是黑點。這時候染色發生了沖突,說明遇見了奇環。這時候就需要找到兩個點在帶花樹中的lca,然后把這整個環縮成一個點。(直接開花。)

縮點(開花)過程:
1、找x和y的LCA(的根)L。找LCA可以用各種方法。。。直接樸素也行。
2、在pre數組中把x和y接起來(表示它們形成環了!)
3、從x、y分別走到L,維護并查集使得變成一棵樹,同時沿路把pre數組連接起來。

題目:

UOJ79 一般圖最大匹配
從前一個和諧的班級,所有人都是搞OI的。有 n 個是男生,有 0 個是女生。男生編號分別為 1,…,n。
現在老師想把他們分成若干個兩人小組寫動態仙人掌,一個人負責搬磚另一個人負責吐槽。每個人至多屬于一個小組。
有若干個這樣的條件:第 v 個男生和第 u 個男生愿意組成小組。
請問這個班級里最多產生多少個小組?

題解:

就是帶花樹的模板題

代碼:

#include <bits/stdc++.h> #define ll long long using namespace std; const int N = 510; const int M = 3e5+10; struct node{ int to,nxt; }g[M]; int head[N],cnt; int vis[N],match[N],f[N],pre[N],Id,id[N]; //vis[i]: 0(未染色) 1(黑色) 2(白色) //match[i]: i的匹配點 //f[i]: i在帶花樹中的祖先 //pre[i]: i的非匹配邊的另一點 //id: 找LCA用 int n,m,ans,u,v; queue<int> q; void Init() {Id=ans=cnt=0;for(int i=1;i<=n;i++)head[i]=-1,id[i]=match[i]=0; } void add(int u,int v){ g[cnt]=node{v,head[u]},head[u]=cnt++; } int getf(int x){ return f[x]==x?x:f[x]=getf(f[x]); } //查詢x和y在帶花樹中的LCA int LCA(int x,int y) {//沿著增廣路向上找lca for(++Id;;swap(x,y))//x,y交替向上 if(x){x=getf(x);//有可能環中有環(花中有花),所以用并查集找祖先,只處理祖先節點 if(id[x]==Id) return x; //x,y在同一環中,一定會找到已被編號的點,該點即為LCA。 else id[x]=Id,x=pre[match[x]];//給點編號,并沿著非匹配邊向上找 } } //縮點(開花 void blossom(int x,int y,int l)//,將x、y到LCA(l)路徑中的點,縮為一點int l) { while(getf(x)!=l){//增廣路取反 pre[x]=y;y=match[x];//如果x、y的奇環中有白點,將其染為黑點,放入隊列,讓其去找不是環中的匹配點 if(vis[y]==2) vis[y]=1,q.push(y);//只改變是根的點 if(getf(x)==x) f[x]=l;if(getf(y)==y) f[y]=l;//增廣路取反 x=pre[y];} } bool aug(int s) {//每次都以s為起點bfs,建帶花樹 for(int i=1;i<=n;i++)vis[i]=pre[i]=0,f[i]=i; while(!q.empty()) q.pop();q.push(s),vis[s]=1;while(!q.empty()){u=q.front();q.pop();for(int i=head[u];~i;i=g[i].nxt){v=g[i].to;//如果已經在同一個環(花)中或者是白點(意為這已經有匹配點),只接跳過 //這種情況不會增加匹配數 if(getf(u)==getf(v)||vis[v]==2) continue;//如果沒有被染色 if(!vis[v]){//先染為白色,將前繼點指向u vis[v]=2;pre[v]=u;//如果沒有被匹配過,直接匹配成功 if(!match[v]){//增廣路取反 for(int x=v,last;x;x=last)last=match[pre[x]],match[x]=pre[x],match[pre[x]]=x; return 1;}//如果被匹配過,則把匹配v的點染為黑色,放入隊列中 vis[match[v]]=1;q.push(match[v]);}//v是黑色,形成奇環,則縮點(開花)。 else {int lca=LCA(u,v);blossom(u,v,lca);blossom(v,u,lca);}} }return 0; } int main(void) {while(~scanf("%d%d",&n,&m)) {Init();while(m--){scanf("%d%d",&u,&v);add(u,v),add(v,u); } for(int i=1;i<=n;i++)if(!match[i]&&aug(i))ans++; printf("%d\n",ans);for(int i=1;i<=n;i++)printf("%d%c",match[i]," \n"[i==n]);}return 0; } 創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

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

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