【HDU 2255】奔小康赚大钱 (最佳二分匹配KM算法)
?
奔小康賺大錢
Time Limit: 1000/1000 MS (Java/Others)????Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1836????Accepted Submission(s): 798
這可是一件大事,關系到人民的住房問題啊。村里共有n間房間,剛好有n家老百姓,考慮到每家都要有房住(如果有老百姓沒房子住的話,容易引起不安定因素),每家必須分配到一間房子且只能得到一間房子。
另一方面,村長和另外的村領導希望得到最大的效益,這樣村里的機構才會有錢.由于老百姓都比較富裕,他們都能對每一間房子在他們的經濟范圍內出一定的價格,比如有3間房子,一家老百姓可以對第一間出10萬,對第2間出2萬,對第3間出20萬.(當然是在他們的經濟范圍內).現在這個問題就是村領導怎樣分配房子才能使收入最大.(村民即使有錢購買一間房子但不一定能買到,要看村領導分配的).
?
Input 輸入數據包含多組測試用例,每組數據的第一行輸入n,表示房子的數量(也是老百姓家的數量),接下來有n行,每行n個數表示第i個村名對第j間房出的價格(n<=300)。?
Output 請對每組數據輸出最大的收入值,每組的輸出占一行。?
Sample Input 2 100 10 15 23?
Sample Output 123?
【分析】
只是打一下模版。最佳二分匹配KM算法。
?
來來上代碼:
1 #include<cstdio> 2 #include<cstdlib> 3 #include<cstring> 4 #include<iostream> 5 #include<algorithm> 6 #include<queue> 7 using namespace std; 8 #define Maxn 310 9 #define Maxm 160010 10 #define INF 0xfffffff 11 12 struct node 13 { 14 int x,y,c,next; 15 }t[Maxm];int len; 16 int first[Maxn]; 17 18 void ins(int x,int y,int c) 19 { 20 t[++len].x=x;t[len].y=y;t[len].c=c; 21 t[len].next=first[x];first[x]=len; 22 } 23 24 int mymin(int x,int y) {return x<y?x:y;} 25 int mymax(int x,int y) {return x>y?x:y;} 26 27 int n; 28 int match[Maxn],lx[Maxn],ly[Maxn];//lx,ly 可行頂標 29 bool visx[Maxn],visy[Maxn]; 30 int slack[Maxn]; 31 32 bool ffind(int x) 33 { 34 visx[x]=1; 35 for(int i=first[x];i;i=t[i].next) if(!visy[t[i].y]) 36 { 37 int y=t[i].y; 38 if(t[i].c==lx[x]+ly[y]) 39 { 40 visy[y]=1; 41 if(!match[y]||ffind(match[y]))//這里可以理解為有向的增廣路,只在一段記錄match 42 { 43 match[y]=x; 44 return 1; 45 } 46 } 47 else slack[y]=mymin(slack[y],lx[x]+ly[y]-t[i].c); 48 } 49 return 0; 50 } 51 52 void solve() 53 { 54 memset(match,0,sizeof(match)); 55 memset(lx,0,sizeof(lx)); 56 memset(ly,0,sizeof(ly)); 57 for(int i=1;i<=n;i++) 58 for(int j=first[i];j;j=t[j].next) lx[i]=mymax(lx[i],t[j].c); 59 60 for(int i=1;i<=n;i++) 61 { 62 for(int j=1;j<=n;j++) 63 slack[j]=INF; 64 while(1) 65 { 66 memset(visx,0,sizeof(visx)); 67 memset(visy,0,sizeof(visy)); 68 if(ffind(i)) break; 69 int delta=INF; 70 for(int j=1;j<=n;j++) 71 { 72 if(!visy[j]) 73 { 74 delta=mymin(delta,slack[j]); 75 } 76 } 77 if(delta==INF) return; 78 for(int j=1;j<=n;j++) 79 { 80 if(visx[j]) lx[j]-=delta; 81 if(visy[j]) ly[j]+=delta; 82 else slack[j]-=delta; 83 } 84 } 85 } 86 } 87 88 int main() 89 { 90 while(scanf("%d",&n)!=EOF) 91 { 92 len=0; 93 memset(first,0,sizeof(first)); 94 for(int i=1;i<=n;i++) 95 { 96 for(int j=1;j<=n;j++) 97 { 98 int x; 99 scanf("%d",&x); 100 ins(i,j,x); 101 } 102 } 103 solve(); 104 int ans=0; 105 for(int i=1;i<=n;i++) ans+=lx[i]+ly[i]; 106 printf("%d\n",ans); 107 } 108 return 0; 109 } HDU 2255?
?
?
?
基礎:
?
二分圖:簡單來說,如果圖中點可以被分為兩組,并且使得所有邊都跨越組的邊界,則這就是一個二分圖。準確地說:把一個圖的頂點劃分為兩個不相交集?UU?和VV?,使得每一條邊都分別連接UU、VV中的頂點。如果存在這樣的劃分,則此圖為一個二分圖。二分圖的一個等價定義是:不含有「含奇數條邊的環」的圖。圖 1 是一個二分圖。為了清晰,我們以后都把它畫成圖 2 的形式。
匹配:在圖論中,一個「匹配」(matching)是一個邊的集合,其中任意兩條邊都沒有公共頂點。例如,圖 3、圖 4 中紅色的邊就是圖 2 的匹配。
??????
我們定義匹配點、匹配邊、未匹配點、非匹配邊,它們的含義非常顯然。例如圖 3 中 1、4、5、7 為匹配點,其他頂點為未匹配點;1-5、4-7為匹配邊,其他邊為非匹配邊。
最大匹配:一個圖所有匹配中,所含匹配邊數最多的匹配,稱為這個圖的最大匹配。圖 4 是一個最大匹配,它包含 4 條匹配邊。
完美匹配:如果一個圖的某個匹配中,所有的頂點都是匹配點,那么它就是一個完美匹配。圖 4 是一個完美匹配。顯然,完美匹配一定是最大匹配(完美匹配的任何一個點都已經匹配,添加一條新的匹配邊一定會與已有的匹配邊沖突)。但并非每個圖都存在完美匹配。
舉例來說:如下圖所示,如果在某一對男孩和女孩之間存在相連的邊,就意味著他們彼此喜歡。是否可能讓所有男孩和女孩兩兩配對,使得每對兒都互相喜歡呢?圖論中,這就是完美匹配問題。如果換一個說法:最多有多少互相喜歡的男孩/女孩可以配對兒?這就是最大匹配問題。
基本概念講完了。求解最大匹配問題的一個算法是匈牙利算法,下面講的概念都為這個算法服務。
交替路:從一個未匹配點出發,依次經過非匹配邊、匹配邊、非匹配邊…形成的路徑叫交替路。
增廣路:從一個未匹配點出發,走交替路,如果途徑另一個未匹配點(出發的點不算),則這條交替路稱為增廣路(agumenting path)。例如,圖 5 中的一條增廣路如圖 6 所示(圖中的匹配點均用紅色標出):
增廣路有一個重要特點:非匹配邊比匹配邊多一條。因此,研究增廣路的意義是改進匹配。只要把增廣路中的匹配邊和非匹配邊的身份交換即可。由于中間的匹配節點不存在其他相連的匹配邊,所以這樣做不會破壞匹配的性質。交換后,圖中的匹配邊數目比原來多了 1 條。
我們可以通過不停地找增廣路來增加匹配中的匹配邊和匹配點。找不到增廣路時,達到最大匹配(這是增廣路定理)。匈牙利算法正是這么做的。在給出匈牙利算法 DFS 和 BFS 版本的代碼之前,先講一下匈牙利樹。
匈牙利樹一般由 BFS 構造(類似于 BFS 樹)。從一個未匹配點出發運行 BFS(唯一的限制是,必須走交替路),直到不能再擴展為止。例如,由圖 7,可以得到如圖 8 的一棵 BFS 樹:
???? ??
這棵樹存在一個葉子節點為非匹配點(7 號),但是匈牙利樹要求所有葉子節點均為匹配點,因此這不是一棵匈牙利樹。如果原圖中根本不含 7 號節點,那么從 2 號節點出發就會得到一棵匈牙利樹。這種情況如圖 9 所示(順便說一句,圖 8 中根節點 2 到非匹配葉子節點 7 顯然是一條增廣路,沿這條增廣路擴充后將得到一個完美匹配)。
?
?
?
KM算法(最佳完美匹配)
?
二分圖的最佳完美匹配
如果二分圖的每條邊都有一個權(可以是負數),要求一種完備匹配方案,使得所有匹配邊的權和最大,記做最佳完美匹配。(特殊的,當所有邊的權為1時,就是最大完備匹配問題)?
我們使用KM算法解決該問題。
KM(Kuhn and Munkres)算法,是對匈牙利算法的一種貪心擴展,如果對匈牙利算法還不夠明白,建議先重新回顧一下匈牙利算法。
KM是對匈牙利算法的一種貪心擴展,這種貪心不是對邊的權值的貪心,算法發明者引入了一些新的概念,從而完成了這種擴展。
?
可行頂標
對于原圖中的任意一個結點,給定一個函數L(node)求出結點的頂標值。我們用數組lx(x)記錄集合X中的結點頂標值,用數組ly(y)記錄集合Y中的結點頂標值。?
并且,對于原圖中任意一條邊edge(x,y),都滿足
?
相等子圖
相等子圖是原圖的一個生成子圖(生成子圖即包含原圖的所有結點,但是不包含所有的邊),并且該生成子圖中只包含滿足
lx(x)+ly(y)=weight(x,y)的邊,這樣的邊我們稱之為可行邊。
?
算法原理
-
定理:如果原圖的一個相等子圖中包含完備匹配,那么這個匹配就是原圖的最佳二分圖匹配。
-
證明 :由于算法中一直保持頂標的可行性,所以任意一個匹配的權值之和肯定小于等于所有結點的頂標之和,則相等子圖中的完備匹配肯定是最優匹配。
這就是為什么我們要引入可行頂標和相等子圖的概念。?
上面的證明可能太過抽象,我們結合圖示更直觀的表述。
該圖表示原圖,且X=1,2,3,Y=4,5,6,給出權值
weight(1,4)=5?
weight(1,5)=10?
weight(1,6)=15?
weight(2,4)=5?
weight(2,5)=10?
weight(3,4)=10?
weight(3,6)=20
對于原圖的任意一個匹配M
那么對于
edge(1,6)weight(1,6)=15?
edge(2,5)weight(2,5)=10?
edge(3,4)weight(3,4)=10
都滿足
lx(x)+ly(y)>=weight(x,y)?
所以
∑i=1xi∈Xlx(xi)+∑i=1yi∈Yly(yi)=K>=∑weight(xi,yi)?
可以看出,一個匹配中的邊權之和最大為K。
那么很顯然,當一個匹配G?的邊權之和恰好為K時,那么G?就是二分圖的最佳完美匹配。
如果對于每一條邊edge(xi,yi)都滿足
lx(xi)+ly(yi)==weight(xi,yi)
那么
?
相等子圖的完備匹配(完美匹配)即滿足上述條件(因為相等子圖的每條邊都是可行邊,可行邊滿足lx(xi)+ly(yi)=weight(xi,yi))所以當相等子圖有完備匹配的時候,原圖有最佳完美匹配。
?
KM的算法流程
流程
Kuhn-Munkras算法(即KM算法)流程:
KM算法的核心部分即控制修改可行頂標的策略使得最終可到達一個完美匹配。
現在我們的問題是,遵循什么樣的原則去修改頂標的值?
對于正在增廣的增廣路徑上屬于集合X的所有點減去一個常數delta,屬于集合Y的所有點加上一個常數delta。
為什么要這樣做呢,我們來分析一下:?
對于圖中任意一條邊edge(i,j)?(其中xi∈X,xj∈Y)權值為weight(i,j)
1、如果i和j都屬于增廣路,那么lx[i]?delta+ly[j]?+delta=lx[i]+ly[j]值不變,也就說edge(i,j)可行性不變,原來是相等子圖的邊就還是,原來不是仍然不是
2、如果i屬于增廣路,j不屬于增廣路,那么lx[i]?delta+ly[j]的值減小,也就是原來這條邊不在相等子圖中(否則j就會被遍歷到了),現在可能就會加入到相等子圖。
3、如果i不屬于增廣路,j屬于增廣路,那么lx[i]+ly[j]+delta的值增大,也就是說原來這條邊不在相等子圖中(否則j就會被遍歷到了),現在還不可能加入到相等子圖
4、如果i,j都不屬于增廣路,那么lx[i]和ly[j]都不會加減常數delta值不變,可行性不變
?
//看成匈牙利樹更容易理解。
你每次i++,選一個為匹配的x點進行增廣,找到以i為起點的匈牙利樹。
如果長這樣:
,那么你就找到了增廣路,i的工作就完成了,i++即可,不需改變頂標。
否則,你的匈牙利樹長這樣,
你會把整棵樹完整的找一遍。我的意思是,能走到的點的vis值都會標記到的。
上面說的“在增廣路上”實際上是“在匈牙利樹上”。
解釋一下第2、3種情況。
2、若x在匈牙利樹上,y不在,說明x->y并非相等子圖上的邊,不然因為找不到增廣路,相等子圖上的邊我都會遍歷一遍,所有點都會打上vis標記。
3、若x不在匈牙利樹上,y在,x一定是i后面的點,它的匹配會在后面使其成立,所以可以看成不是相等子圖上的邊?【其實這個我還覺得有點迷吧。。但是頂標只是增加,所以不會錯的。
//有slack的y必定有一條邊連向一個打過vis標記的點【看代碼這個就明白了】
【以上是個人的傻逼見解啊。。2017-04-25 10:44:23】
?
這 樣,在進行了這一步修改操作后,圖中原來的可行邊仍可行,而原來不可行的邊現在則可能變為可行邊。那么delta的值應取多少?
觀察上述四種情況,只有第二類邊(xi∈X,yj∈Y)的可行性經過修改可以改變。
因為對于每條邊都要滿足lx(i)+ly(j)>=weight(i,j),這一性質絕對不可以改變,所以取第二種情況的?lx[i]+ly[j]?weight(i,j)的最小值作為delta。
證明 :?
delta=Min(lx[i]+ly[j]?weight(i,j))=lx[i]+ly[j]?Max(weight(i,j))
第二類邊 :?
成立
?
下面我們重新回顧一下整個KM算法的流程 :
?
?
?
下面用圖模擬:
初始化標桿使X標桿的值為斜體字的值。?
連接每條邊并且使得x1和y3匹配,然后遍歷x2,發現x2找不到合法增廣路。?
把不合法路徑上的x點都歸為點集S,y點都歸為T,將不在T中的y點和在S中的點嘗試進行加邊。?
找到兩條邊,更新頂標之后,成功形成增廣路,運用匈牙利算法反選。?
給x3找一個合法的增廣路,一下就找到了,直接反選,結束。?
?
KM算法的優化
KM算法可以優化到O(n3)
一個優化是對Y頂點引入松弛函數slack,slack[j]保存跟當前節點j相連的節點i的lx[i]+ly[j]?weight(i,j)的最小值,于是求delta時只需O(n)枚舉不在交錯樹中的Y頂點的最小slack值即可。
松弛值可以在匈牙利算法檢查相等子樹邊失敗時進行更新,同時在修改標號后也要更新
?
?
轉自:
http://www.renfei.org/blog/bipartite-matching.html
http://blog.csdn.net/sixdaycoder/article/details/47720471
http://blog.csdn.net/zxn0803/article/details/49999267
?
?
?
?
2016-10-26?21:12:20
?
轉載于:https://www.cnblogs.com/Konjakmoyu/p/6001900.html
總結
以上是生活随笔為你收集整理的【HDU 2255】奔小康赚大钱 (最佳二分匹配KM算法)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: - (BOOL)applicationS
- 下一篇: 20150210--Smarty1-02