知識點:二分圖最大權匹配


知識點:二分圖最大權匹配

知識點概要:

二分圖最大權匹配是指在二分圖中,每一條邊都會有一個權值,詢問的是匹配之后的最大權值而不是最大的匹配數,主要的做法有KM和最費用流的做法。

知識點詳解:

二分圖最大權匹配的主要做法是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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM