看了兩篇博客,覺得寫得不錯,便收藏之。。
首先是第一篇,轉自某Final牛
帶花樹……其實這個算法很容易理解,但是實現起來非常奇葩(至少對我而言)。
比如昨晚找到一篇鄙視帶花樹的論文,然后介紹了一種O(E)的一般圖最大匹配……我以為找到了神論文,然后ACM_DIY眾神紛紛表示這個是錯的……於是神論文成為了”神論文“……
又比如圍觀nocow上帶花樹標程,一看……這顯然是裸的匈牙利算法……貨不對板啊
當然……如果二分圖的匈牙利算法還不會請先圍觀求二分圖最大匹配的匈牙利算法。
實際上任意圖求最大匹配也是找增廣路,但是由於奇環的出現,找增廣路變得困難。
首先明確一點,增廣路上是不能有重復出現的點的。
二分圖中,匹配邊可以看作是有向的,比如定義總是從X集指向Y集。假若定義了起點必須在X集中,那么增廣路中出現該匹配邊時,必然是按照這個方向的。所以一個點在增廣路中的奇偶性是確定的。
而這個圖中,從增廣路3->1->4->5和2->4->1->6可以看出,對於有奇環的任意圖,1和4這兩個點在增廣路中所在位置的奇偶性不再一定。於是我們考慮處理這些奇環。
定義奇環:包含2k+1個點和k條匹配邊的一個環。(如果不是這樣,我們找增廣路不會走上去)
對於這個奇環,k條匹配覆蓋了2k個點,那么顯然有一個點未被覆蓋。我們拿出這個點來討論。
比如圖中的1號點就是這個這個特殊的點。除了這個點以外,其它的點都被覆蓋了,所以只能向外連非匹配邊,而1號點可以向外連匹配邊
或非匹配邊。
如果1號點沒有被外面的點匹配,那么無論從其它的哪個點走進來,都能以1為終點找到增廣路。(要么順時針跑到1,要么逆時針)
同理如果1號點被外面的點匹配了,那么無論從其它的哪個點走進來,都能把這個圈看成一個點,然后從1的那條匹配邊穿出去。(要么順時針,要么逆時針)
於是這個奇環就可以看成一個點,其主要特性由1號點體現(諸如和誰匹配了之流)。
這個合成點就叫做花。這個算法的思想就是不斷地把奇環合成點,直至找到增廣路(合成了某朵花以后就把整朵花當成一個點)。
考慮用BFS搜索增廣路。
圍觀wiki這個圖
由於BFS的性質,我們找到奇環只能是和同層的點,或者下下一層的點。
然后奇環的關鍵點必然是這棵BFS樹里深度最淺的點。然后考慮合成以后,花如何展開對應的路徑,使得我們能夠增廣。
花套花這個東西想起來都糾結>_<。
amber的程序里面並沒有把點真的合成,只是弄了一個表示集合的標號:Base,然后鄰接矩陣就不用變來變去了。
對於花中連向父親的是匹配邊的點,他的增廣路顯然是直接順着父親走,而如果連向父親的邊是非匹配邊的點,那么顯然是往后走然后跑過紅色的橫插邊,然后再向上跑回關鍵點。
注意到如果連向父子的邊是匹配邊的點原先是不需要Father這個域來描述的,直接用表示匹配的那個域就可以了。但是現在在花中,他的Father這個域就要起作用了,用來向后指向,然后繞過紅色橫插邊然后再跑回關鍵點。
實在是太精妙了。
1 //Problem:http://acm.timus.ru/problem.aspx?space=1&num=1099 2 #include <cstdio> 3 #include <cstdlib> 4 #include <cstring> 5 #include <iostream> 6 #include <algorithm> 7 using namespace std; 8 const int N=250; 9 int n; 10 int head; 11 int tail; 12 int Start; 13 int Finish; 14 int link[N]; //表示哪個點匹配了哪個點 15 int Father[N]; //這個就是增廣路的Father……但是用起來太精髓了 16 int Base[N]; //該點屬於哪朵花 17 int Q[N]; 18 bool mark[N]; 19 bool map[N][N]; 20 bool InBlossom[N]; 21 bool in_Queue[N]; 22 23 void CreateGraph(){ 24 int x,y; 25 scanf("%d",&n); 26 while (scanf("%d%d",&x,&y)!=EOF) 27 map[x][y]=map[y][x]=1; 28 } 29 30 void BlossomContract(int x,int y){ 31 fill(mark,mark+n+1,false); 32 fill(InBlossom,InBlossom+n+1,false); 33 #define pre Father[link[i]] 34 int lca,i; 35 for (i=x;i;i=pre) {i=Base[i]; mark[i]=true; } 36 for (i=y;i;i=pre) {i=Base[i]; if (mark[i]) {lca=i; break;} } //尋找lca之旅……一定要注意i=Base[i] 37 for (i=x;Base[i]!=lca;i=pre){ 38 if (Base[pre]!=lca) Father[pre]=link[i]; //對於BFS樹中的父邊是匹配邊的點,Father向后跳 39 InBlossom[Base[i]]=true; 40 InBlossom[Base[link[i]]]=true; 41 } 42 for (i=y;Base[i]!=lca;i=pre){ 43 if (Base[pre]!=lca) Father[pre]=link[i]; //同理 44 InBlossom[Base[i]]=true; 45 InBlossom[Base[link[i]]]=true; 46 } 47 #undef pre 48 if (Base[x]!=lca) Father[x]=y; //注意不能從lca這個奇環的關鍵點跳回來 49 if (Base[y]!=lca) Father[y]=x; 50 for (i=1;i<=n;i++) 51 if (InBlossom[Base[i]]){ 52 Base[i]=lca; 53 if (!in_Queue[i]){ 54 Q[++tail]=i; 55 in_Queue[i]=true; //要注意如果本來連向BFS樹中父結點的邊是非匹配邊的點,可能是沒有入隊的 56 } 57 } 58 } 59 60 void Change(){ 61 int x,y,z; 62 z=Finish; 63 while (z){ 64 y=Father[z]; 65 x=link[y]; 66 link[y]=z; 67 link[z]=y; 68 z=x; 69 } 70 } 71 72 void FindAugmentPath(){ 73 fill(Father,Father+n+1,0); 74 fill(in_Queue,in_Queue+n+1,false); 75 for (int i=1;i<=n;i++) Base[i]=i; 76 head=0; tail=1; 77 Q[1]=Start; 78 in_Queue[Start]=1; 79 while (head!=tail){ 80 int x=Q[++head]; 81 for (int y=1;y<=n;y++) 82 if (map[x][y] && Base[x]!=Base[y] && link[x]!=y) //無意義的邊 83 if ( Start==y || link[y] && Father[link[y]] ) //精髓地用Father表示該點是否 84 BlossomContract(x,y); 85 else if (!Father[y]){ 86 Father[y]=x; 87 if (link[y]){ 88 Q[++tail]=link[y]; 89 in_Queue[link[y]]=true; 90 } 91 else{ 92 Finish=y; 93 Change(); 94 return; 95 } 96 } 97 } 98 } 99 100 void Edmonds(){ 101 memset(link,0,sizeof(link)); 102 for (Start=1;Start<=n;Start++) 103 if (link[Start]==0) 104 FindAugmentPath(); 105 } 106 107 void output(){ 108 fill(mark,mark+n+1,false); 109 int cnt=0; 110 for (int i=1;i<=n;i++) 111 if (link[i]) cnt++; 112 printf("%d\n",cnt); 113 for (int i=1;i<=n;i++) 114 if (!mark[i] && link[i]){ 115 mark[i]=true; 116 mark[link[i]]=true; 117 printf("%d %d\n",i,link[i]); 118 } 119 } 120 121 int main(){ 122 // freopen("input.txt","r",stdin); 123 CreateGraph(); 124 Edmonds(); 125 output(); 126 return 0; 127 }
然后還有一篇,鏈接請猛戳。。
在北京冬令營的時候,yby提到了“帶花樹開花”算法來解非二分圖的最大匹配。
http://builtinclz.abcz8.com/art/2012/ural1099.cpp
沒錯,這是用來解決URAL 1099 Work Schedule那題的。時間復雜度是O(N^3)