[TOC]php
###知識點:二分圖最大權匹配c++
###知識點概要: 二分圖最大權匹配是指在二分圖中,每一條邊都會有一個權值,詢問的是匹配以後的最大權值而不是最大的匹配數,主要的作法有KM和最費用流的作法。git
###知識點詳解: 二分圖最大權匹配的主要作法是KM和費用流。而費用流的作法是比較簡單的,新建一個源點和一個匯點,而後從源點向每個左集合中的點連一條流量1,費用爲0的邊,從右集合中的點向匯點連一條流量爲1,費用爲0的邊,最後再將原圖中的邊連起來,設置成流量爲1,費用爲權值的邊,這樣跑一邊最小費用最大流以後,獲得的就是二分圖的最大權匹配。 接下來開始講KM的作法。KM的作法的核心點在於對於二分圖左右兩個集合中的每個點分別記一個標杆數組$X[i]$,$Y[i]$,而且在算法執行的仍以時刻,咱們都要保持$X[i]+Y[j]>=w(i,j)$這個性質。在理解KM算法以前,咱們須要具體的理解這個標杆數組在KM算法中的做用。首先咱們須要知道KM算法的正確性基於這個定理: 若二分圖中由全部$X[i]+Y[j]=w(i,j)$的邊$(i,j)$組成的導出子圖具備完備匹配,那麼這個完備匹配就是原來二分圖中的最大權匹配。 這爲何是正確的呢?咱們以前在定義標杆數組的時候,就保證了$X[i]+Y[j]>=w(i,j)$這個性質在仍以時刻都會成立。而咱們導出的子圖因爲由完備匹配,因此這個完備匹配的權值必定就是全部標杆數組的和,而又根據$X[i]+Y[j]>=w(i,j)$,因此咱們在原圖中任意其餘的匹配方案,都沒法比這個匹配方案更加優秀。因此咱們的導出子圖的完備匹配的權值就是原圖的最大權匹配。 因而咱們接下來就須要求這個完備匹配了。咱們先初始化標杆數組$X$爲與這個點相連的邊的最大權值,初始$Y$爲0,這樣咱們就先保證了在初始狀況下知足咱們的標杆數組的性質。可是如今的問題在於,對於如今的標杆數組$X$和$Y$,咱們的導出子圖並不必定是存在完備匹配的,因而咱們就須要調整這個標杆數組使得這個導出子圖可以包括原圖中更多的邊,來讓它具備完備匹配。而且咱們須要這個保證這個調整的量要足夠小,讓它不會增長一些沒必要要的邊致使答案變劣。因而咱們能夠這樣進行調整:咱們經過調整以前的最後一次$Dfs$交錯路,找到全部$i$被訪問過的,可是$j$並無被訪問到的邊$(i,j)$,記$d$爲$X[i]+Y[j]-w(i,j)$的最小值,而後把每個左集合被訪問的點的標杆$X[i]$減去$d$,每個右集合被訪問的點的標杆$Y[i]$加上$d$。而後繼續求完備匹配。爲何這樣的方法是正確的呢,咱們這樣分析: 1.對於已經在導出子圖中的邊$(i,j)$,因爲$X[i]$減小了$d$,$Y[i]$增長了$d$,因此這個標杆和仍是不變的,仍然在這個導出子圖中。 2.對於不在導出子圖的邊$(i,j)$,若是$i$被訪問過了,$j$沒有被訪問過,那麼這個標杆和$X[i]+Y[j]$會變小,那麼這個標杆和就有可能等於$w(i,j)$,因此這條邊就會有可能加入這個導出子圖。 3.對於不在導出子圖的邊$(i,j)$,若是$i$沒有被訪問過,$j$被訪問過了,那麼這個標杆和$X[i]+Y[j]$就會變大,那麼這條邊仍然不能加入導出子圖。 這樣咱們就能夠證實這種修改的方式是正確的了。因此咱們如今就能夠得出二分圖最大權匹配的步驟了: 1.初始化標杆數組 2.枚舉每個點進行二分圖的匹配 3.若是這個點沒有匹配到,那麼修改標杆數組繼續進行匹配 4.若是這個點匹配到了,那麼繼續匹配下一個點 最後把全部的匹配邊的權值加起來就是最大權匹配的答案了。 可是咱們分析一下,枚舉每個點,須要$O(n)$的複雜度,求出標杆數組的修改量,須要$O(n^2)$的複雜度,每一個點最多修改$O(n)$次,因此總複雜度爲$O(n^4)$。而後咱們觀察咱們這些步驟,發現咱們複雜度的瓶頸實際是在求標杆數組的修改量上的。由於枚舉點是不可避免的,而後每一個點的修改也是指望的,因此咱們只能對求修改量的方法進行優化。樸素的方法是枚舉每個訪問的$i$和未訪問的$j$,而後咱們能夠對此進行優化,記$slack$數組爲鬆弛變量,每次訪問到一個$i$的時候,就把與這個點相連的點$j$,而且當前沒法加入導出子圖的點,用$X[i]+Y[j]-w(i,j)$來更新$slack[j]$。而後咱們只須要枚舉每個右集合中未訪問到的點$v$,而後用$slack[v]$更新修改量$d$,這樣就能夠作到求修改量的複雜度爲$O(n)$的了,而後總複雜度就能夠優化成$O(n^3)$的了。不過還要注意的是,修改右集合中的標杆$Y[i]$的時候,也要同時修改$slack[i]$。算法
###模板 HDU2255 奔小康賺大錢 題目傳送門數組
####Code優化
#pragma GCC optimize (3,"inline","Ofast") #include <bits/stdc++.h> using namespace std; typedef long long ll; bool Finish_read; template<class T>inline void read(T &x){Finish_read=0;x=0;int f=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-')f=-1;if(ch==EOF)return;ch=getchar();}while(isdigit(ch))x=x*10+ch-'0',ch=getchar();x*=f;Finish_read=1;} template<class T>inline void print(T x){if(x/10!=0)print(x/10);putchar(x%10+'0');} template<class T>inline void writeln(T x){if(x<0)putchar('-');x=abs(x);print(x);putchar('\n');} template<class T>inline void write(T x){if(x<0)putchar('-');x=abs(x);print(x);} /*================Header Template==============*/ const int N=505; const int inf=2e9+7; int n; int wx[N],wy[N],weight[N][N],slack[N]; int belong[N],visx[N],visy[N]; /*==================Define Area================*/ int FindPath(int u) { visx[u]=1; for(int v=1;v<=n;v++) { if(visy[v]) continue; int t=wx[u]+wy[v]-weight[u][v]; if(!t) { visy[v]=1; if(belong[v]==-1||FindPath(belong[v])) { belong[v]=u; return 1; } } else if(slack[v]>t) slack[v]=t; } return 0; } int Km() { for(int i=1;i<=n;i++) wx[i]=-inf; for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { wx[i]=max(wx[i],weight[i][j]); } } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { slack[j]=inf; } while(1) { memset(visx,0,sizeof visx); memset(visy,0,sizeof visy); if(FindPath(i)) break; int ret=inf; for(int j=1;j<=n;j++) { if(!visy[j]&&ret>slack[j]) ret=slack[j]; } for(int j=1;j<=n;j++) { if(visx[j]) wx[j]-=ret; } for(int j=1;j<=n;j++) { if(visy[j]) wy[j]+=ret; else slack[j]-=ret; } } } int ans=0; for(int i=1;i<=n;i++) { if(~belong[i]) ans+=weight[belong[i]][i]; } return ans; } int main() { while(scanf("%d",&n)!=EOF) { memset(belong,-1,sizeof belong); for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { read(weight[i][j]); } } printf("%d\n",Km()); } return 0; }