【轉】二分圖最大權匹配的算法

原題如下:

 

隨着社會的不斷髮展,人與人之間的感情越來越功利化。最近,愛神丘比特發現,愛情也已不再是完全純潔的了。這使得丘比特很是苦惱,他越來越難找到合適的男女,並向他們射去丘比特之箭。於是丘比特發了一封郵件給月下老人——掌管東方人愛情的神,向他求教。

月下老人回信告訴丘比特,純潔的愛情並不是不存在,而是他沒有找到。在東方,人們講究的是緣分。月下老人只要做一男一女兩個泥人,在他們之間連上一條紅線,那麼它們所代表的人就會相愛——無論他們身處何地。而丘比特的愛情之箭只能射中兩個距離相當近的人,選擇的範圍自然就小了很多,不能找到真正的有緣人。

丘比特聽了月下老人的解釋,茅塞頓開,於是用了人間的最新科技改造了自己的弓箭,使得丘比特之箭的射程大大增加。這樣,射中有緣人的機會也增加了不少。

當然,無論丘比特怎麼改造自己的弓箭,總還是存在缺陷的。首先,弓箭的射程儘管增大了,但畢竟還是有限的,不能像月下老人那樣,做到“千裏姻緣一線牽”。其次,無論怎麼改造,箭的軌跡終歸只能是一條直線,也就是說,如果兩個人之間的連線段上有別人,那麼莫不可向他們射出丘比特之箭,否則,按月下老人的話,就是“亂點鴛鴦譜”了。

情人節(Valentine's day)的午夜零時,丘比特開始了自己的工作。他選擇了一組數目相等的男女,感應到他們互相之間的緣分大小,並依次射出了神箭,使他們產生愛意。他希望能選擇最好的方法,使被他選擇的每一個人都被射中一次,且每一對被射中的人之間的緣分的總和最大。

這時丘比特發現了一個嚴重的問題,他想了好一陣子都想不出來,最後他決定打電話去問一下月下老人。

“我想讓我選擇的每一對男女的總緣分值最大,可是,有的男人同時跟多個女人的緣分值都很大,而有的男人跟所有的女人的緣分值都不大,即使是我對於每個男人都選擇緣分值最大的女人來匹配,但是由於一個男人只能對應一個女人,最終得到的局部最優解也未必是全局最優解啊!”

“呵呵,”月下老人說,“你也意識到這個問題啦,想當年我爲了這個問題也是頭痛了很久。後來我聽說計算機能解決複雜的問題,我就去北大修了個計算機雙學位,後來自己編了個程序,把這個問題解決啦。”

丘比特就向月下老人要那個程序,但是月下老人說他的電腦恰好中了病毒,上不了網,沒法把程序傳給丘比特。這可把丘比特急的。

情急之下,丘比特只好到百度知道上發帖,懸賞10000分徵求能夠求出最佳匹配方案的程序。

現在你看到了丘比特的帖子,你的任務是幫丘比特找到最佳的方案。

輸入文件第一行爲正整數k,表示丘比特之箭的射程,第二行爲正整數n(n=20),隨後有2n行,表示丘比特選中的人的位置,其中前n行爲男子,後n行爲女子。位置是由一對整數表示的座標,它們之間用空格分隔。剩下部分是一個n×n表格,第i行第j列的數表示第i個男子與第j個女子之間的緣分值,在0255之間。

 

分析如下:

這是一個二分圖,左邊20個頂點代表男的,右邊20個頂點代表女的,每一條連接左邊頂點和右邊頂點的邊都有一個對應的權值代表他們的緣分值(用二維數組w[i][j]來表示),如果某一對男女因爲射程或中間有人的限制而不可能成爲一對,那麼相當於把他們之間的邊的權值設爲負無窮。現在問題就是,如何求出連接兩邊的最佳的20條邊(任意兩條邊不能有共同的頂點),使這20條邊的權值之和最大。

那麼有一個問題就是,你如何判斷一個匹配方案是最佳的?

神奇的算法,(好像叫匈牙利算法,或KM算法),就是從這個問題入手的,如果我們能證明所有匹配方案的結果都小於某個值,而且恰好有一個方案的結果是這個值,那麼這個方案就是最佳方案。

如果我們給每個頂點也分配一個權值(稱爲頂標),左邊是lx[i],右邊是ly[j],保證對於每一個ij,都有lx[i]+ly[j]-w[i][j]>=0(下面把lx[i]+ly[j]-w[i][j]叫做邊的判別式)。如果存在一個匹配,所有的邊的判別式都等於0,那麼這個匹配肯定就是原圖中的最佳匹配。

 

但問題是,對於當前的頂標,並不一定存在全部邊的判別式都等於0的匹配。這時,可以用某種方法對頂標進行調整。具體調整方法見後。現在看一下整體的思路(數學歸納法):

初始化:隨便找到一組滿足條件的lx[i]ly[j],這個並不難。

一:先在第一個男人與第一人女人之間尋找最佳匹配,(顯然匹配方式只有一種,所以這一種就是最佳匹配)。

二:假設前x1個男人與前x1個女人已達到最佳匹配,那麼現在要把男x和女x插入,這時男x有兩種選擇,要麼直接跟女x,要麼跟前x1女中的某個(設爲第y1個),如果他選擇後者,那麼這個女y肯定之前已經與某男(記爲linky[y1])匹配,那麼這個linky[y1]先生,如果他選擇女x,那麼遞歸至此結束,如果他選擇的是前x1中的某個y2,那麼那位linky[y2]先生同樣要面臨選擇……直到有人選擇女x爲止(在此之前linky[x]=-1)。

兩男不能同選一女,所以我們用visy[y]來記錄某女是否被選過,已被選過的設爲true,則後面的先生們不會再選到她。同時我們用visx[x]來記錄某男是否面臨過選擇(不管他選了沒有),這個值在調整頂標的時候有用。

 

上面沒有考慮到邊的判別式是否爲0,實際上,當某男臨選擇的時候,只有某個選擇對應的判別式爲0,他纔可以選定。如果最終有一男選定了女x,那麼關於前x男和前x女的最佳匹配已經找到。如果在選到女x前,中途有某男,他面臨的所有選擇的判別式都大於0,那麼尋找最佳匹配就失敗了,這時就需要對頂標進行調整。

首先確定哪一些頂標需要調整,那麼那些面臨過選擇的先生們,他們的頂標要調低,這樣他那些判別式大於0的選擇就可能轉換成等於0的,這樣他們就可以有更多的選擇。

但是在這個過程中,有一些女士已經被某些男士選定,即他們之間的判別式是0,如果男士們調低頂標的話,那麼這些女士的頂標要相應調高,以保持判別式仍爲0

最後是確定調整的幅度,那麼就要看男人們所面臨的所有判別式大於0的選擇中,哪一個的判別式最小,那麼把此最小值(記爲lack)作爲調整的幅度。

程序最核心的代碼如下:

const int maxn=20,OO=2147483647;

int w[maxn][maxn];

int lx[maxn]={0},ly[maxn]={0};

int linky[maxn];

int visx[maxn],visy[maxn];

int lack;

bool find(int x){

    visx[x]=true;

    for(int y=0;y<maxn; y++){

        if(visy[y])continue;

        int t=lx[x]+ly[y]-w[x][y];

        if(t==0){

            visy[y]=true;

            if(linky[y]==-1||find(linky[y])){

                linky[y]=x;

                return true;

            }

        }

        else if(lack>t)lack=t;

    }

    return false;

}

void KM(){

    memset(linky,-1,sizeof(linky));

    for(int i=0;i<maxn; i++)

        for(int j=0;j<maxn; j++)

            if(w[i][j]>lx[i])

                lx[i]=w[i][j]; //初始化頂標

    for(int x=0;x<maxn; x++){

        for(;;){

            memset(visx,0,sizeof(visx));

            memset(visy,0,sizeof(visy));

            lack=OO;

            if(find(x))break;

            for(int i=0;i<maxn; i++){

                if(visx[i])lx[i]-=lack;

                if(visy[i])ly[i]+=lack;

            }

        }

    }

}

算法評價:

我想不到這樣一個問題,竟然可以用這麼少的代碼就解決。而運行速度更是驚人的快,我把人數從20增加到50,再到100,所用時間仍然是0毫秒(不計前面的判斷兩人距離以及兩人之間是否有人的時間),結果我不想再試了。後來用了一個計數變量,得出此算法的時間複雜度大概在n的平方與三次方之間。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章