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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

【学习小记】一般图最大匹配——带花树算法

發(fā)布時間:2025/3/14 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【学习小记】一般图最大匹配——带花树算法 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Text

一般圖的最大匹配仍然是基于尋找增廣路的

增廣路的定義是這樣的一條路徑,它不經(jīng)過重復(fù)的點,并且路徑兩端均沒有匹配,且整條路徑是非匹配邊-匹配邊-非匹配邊這樣交錯的。

類比二分圖最大匹配的增廣路算法,如果我們找到了一條增廣路,那么將這條增廣路的邊取反(匹配的變成非匹配,非匹配的變成匹配),那么匹配數(shù)會恰好+1,如果全圖不存在增廣路,也就說明當前已經(jīng)是一個最大匹配了。(證明略)

一般圖和二分圖的區(qū)別就在于有沒有邊數(shù)為奇數(shù)的環(huán)
那為什么一般圖最大匹配不能直接像二分圖那樣做呢?

考慮我們尋找增廣路的過程
(圖片引自 陳胤伯《淺談圖的匹配算法及其應(yīng)用》,2015年國家集訓隊論文集)

我們在尋找增廣路的時候,會將路徑上的點黑白染色,匹配只存在黑點與白點之間。

如果沒有環(huán)或者只有偶環(huán),那么每個點的顏色(或者說奇偶性)是確定的
但如果出現(xiàn)了奇環(huán),那么點的顏色就不再確定,因為奇環(huán)順時針走一圈和逆時針走一圈的結(jié)果是不同的。

怎么辦呢?

這就有了我們的帶花樹算法(名字我覺得很迷)

我們按照算法流程來一步步說明

仍然是從每個未匹配的點開始尋找增廣路,不過我們采用BFS的方式,每個點均設(shè)為無色,端點染成黑色
設(shè)當前點為u(黑色),枚舉與它相鄰的點v

考慮v是否已經(jīng)被訪問過

若v尚未訪問過(v為無色)
如果v尚未匹配,說明我們找到了一條增廣路,直接返回修改。
如果v已經(jīng)匹配,那么將v染成白色,v的匹配點x加入隊列,繼續(xù)尋找增廣路,x染成黑色。

容易看出,根據(jù)上面的過程,我們只會對于黑點枚舉出邊,正確性是顯然的,并且我們訪問的路徑形成了一棵黑白點交錯的樹。

若v已經(jīng)訪問過(有顏色),說明我們找到了一個環(huán)。
如果它是一個偶環(huán)(v為白色),那么v顯然已經(jīng)被找過了,無需再找一次。

如果它是一個奇環(huán)(v為黑色),這就出鍋了
怎么辦呢?

如上圖,粗邊為匹配邊。
對于一個奇環(huán),我們一定是從一個黑點進入,然后也在黑點碰頭(u,v),(這個可以畫圖理解一下,如果不是從黑點進入,我們在之前訪問這個奇環(huán)時一定還沒有繞一圈就找到增廣路增廣了)

問題在于,此時對于整個奇環(huán)的顏色都不確定了,我們令這個奇環(huán)的頂點為最頂上的那一個黑點,那么考慮u上方的這一個白點,我們既可以走從頂點走一條非匹配邊到它,它作為一個黑點,也可以從另一邊轉(zhuǎn)一圈到它,此時這個它變成了黑點,它是需要加入隊列繼續(xù)走的

也就是說,對于一個奇環(huán),它上面的點都可以成為黑點。
繼續(xù)觀察可以發(fā)現(xiàn),整個奇環(huán)的匹配狀態(tài)只與頂點的匹配狀態(tài)有關(guān),如果在后來的某一次尋找時奇環(huán)上的匹配被改變了,那么頂點的顏色唯一決定了整個環(huán)的匹配邊是如何走的。

也就是說,整個環(huán)就可以用一個頂點表示了,也就意味著我們可以將這個環(huán)縮掉,縮掉的環(huán)就稱為“花”,縮環(huán)就是開花

我們不妨對于每一個白點x,記pre[x]表示x是由哪一個黑點走過來的,也就是記錄了增廣路上的非匹配邊。
對于每一個點,記match[x]表示x的匹配點是誰。

縮環(huán)具體怎么縮呢?

如果直接修改原本的連邊比較麻煩,我們考慮采用并查集,記錄每個點所在的奇環(huán)的頂點,初始時就是它自己。縮環(huán)的時候,我們直接將環(huán)上的所有點并查集父親連向奇環(huán)的頂點,并將環(huán)上的白點都變成黑點,并且加入隊列。

此外,由于奇環(huán)可以雙向走,因此我們的pre邊也要變成雙向的。

容易發(fā)現(xiàn),我們有可能經(jīng)過了多次縮環(huán),也就是說某一次縮環(huán)的一個點很有可能是縮過的一個環(huán)頂,我們在縮環(huán)以及找到增廣路返回修改的時候是需要走原來縮之前的環(huán)的,這個只需要沿著pre和match一直走即可,pre在這里相當于記錄了縮掉的環(huán)內(nèi)部的走法。

現(xiàn)在我們來理一理思路

從每個未匹配的點BFS尋找增廣路,每個點均設(shè)為無色,端點染成黑色
枚舉與當前點u(黑色)相鄰的點v

考慮v是否已經(jīng)被訪問過

若v尚未訪問過(v為無色)
如果v尚未匹配,找到了一條增廣路,直接返回修改。
如果v已經(jīng)匹配,將v染成白色,將v的匹配點x加入隊列,繼續(xù)尋找增廣路,x染成黑色。

若v在當次增廣已經(jīng)訪問過,找到環(huán)
v為白色,是一個偶環(huán),跳過。
v為黑色且u,v所在的奇環(huán)已經(jīng)縮過了,那么也跳過。

否則,v為黑色,找到一個新的奇環(huán),那么找到u,v所在奇環(huán)的環(huán)頂(即它們在BFS上跑出來的交錯樹的lca,稱之為最近公共花祖先),將u到環(huán)頂?shù)穆窂揭约皏到環(huán)頂?shù)穆窂叫薷牡?#xff0c;白點染成黑點,加入隊列,并將環(huán)上的點(或者是某個已經(jīng)縮了的環(huán)頂)并查集父親指向lca。

接下來就是代碼部分,我們可以結(jié)合代碼來理解上面的過程。

Code (UOJ #079)

#include <bits/stdc++.h> #define fo(i,a,b) for(int i=a;i<=b;++i) #define fod(i,a,b) for(int i=a;i>=b;--i) #define N 505 #define M 130005 using namespace std; int n,m,m1,fs[N],nt[2*M],dt[2*M],pre[N],match[N],f[N],bz[N],bp[N],ti,d[N*N]; void link(int x,int y) {nt[++m1]=fs[x];dt[fs[x]=m1]=y; } int getf(int k) {return (f[k]==k)?k:f[k]=getf(f[k]); } int lca(int x,int y)//整個lca實現(xiàn)比較巧妙,由于是BFS,那么這兩個點在當前奇環(huán)上的深度一定相等,交替暴力尋找lca即可。 {ti++;x=getf(x),y=getf(y);while(bp[x]!=ti){bp[x]=ti;//此處僅僅是一個標記,無其他作用x=getf(pre[match[x]]);if(y) swap(x,y);}return x; } void make(int x,int y,int w)//縮環(huán)(開花)過程 {while(getf(x)!=w){pre[x]=y,y=match[x];//x是原本的黑點,y是原本的白點,將原本的pre邊變成雙向。if(bz[y]==2) bz[y]=1,d[++d[0]]=y;//若y還是白點則染黑if(getf(x)==x) f[x]=w;if(getf(y)==y) f[y]=w;x=pre[y];} } bool find(int st)//主過程 {fo(i,1,n) f[i]=i,pre[i]=bz[i]=0;d[d[0]=1]=st,bz[st]=1;int l=0;while(l<d[0]){int k=d[++l];for(int i=fs[k];i;i=nt[i]){int p=dt[i];if(getf(p)==getf(k)||bz[p]==2) continue;//如果找到一個已經(jīng)縮過的奇環(huán)或者偶環(huán)則跳過if(!bz[p]){bz[p]=2,pre[p]=k;if(!match[p])//找到增廣路{for(int x=p,y;x;x=y) y=match[pre[x]],match[x]=pre[x],match[pre[x]]=x;//返回修改匹配return 1;}bz[match[p]]=1,d[++d[0]]=match[p];//否則將其匹配點加入隊列}else {int w=lca(k,p);make(k,p,w);make(p,k,w);//以上分別修改k到lca的路徑以及p到lca的路徑(環(huán)的兩半)}}}return 0; } int main() {cin>>n>>m;fo(i,1,m){int x,y;scanf("%d%d",&x,&y);link(x,y),link(y,x);}int ans=0;fo(i,1,n) if(!match[i]) ans+=find(i);printf("%d\n",ans);fo(i,1,n) printf("%d ",match[i]); }

轉(zhuǎn)載于:https://www.cnblogs.com/BAJimH/p/10569418.html

總結(jié)

以上是生活随笔為你收集整理的【学习小记】一般图最大匹配——带花树算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。