KM算法 最优匹配(最大权匹配) hdu 2255 奔小康赚大钱 最小权匹配 poj 2195 Going Home
? ? 最大權二分匹配問題就是給二分圖的每條邊一個權值,選擇若干不相交的邊,得到的總權值最大。解決這個問題可以用KM算法。理解KM算法需要首先理解“可行頂標”的概念。可行頂標是指關于二分圖兩邊的每個點的一個值lx[i]或ly[j],保證對于每條邊w[i][j]都有lx[i]+ly[j]-w[i][j]>=0。如果所有滿足lx[i]+ly[j]==w[i][j]的邊組成的導出子圖中存在一個完美匹配,那么這個完美匹配肯定就是原圖中的最大權匹配。理由很簡單:這個匹配的權值之和恰等于所有頂標的和,由于上面的那個不等式,另外的任何匹配方案的權值和都不會大于所有頂標的和。
? ? 但問題是,對于當前的頂標的導出子圖并不一定存在完美匹配。這時,可以用某種方法對頂標進行調整。調整的方法是:根據最后一次不成功的尋找交錯路的DFS,取所有i被訪問到而j沒被訪問到的邊(i,j)的lx[i]+ly[j]-w[i][j]的最小值d。將交錯樹中的所有左端點的頂標減小d,右端點的頂標增加d。經過這樣的調整以后:原本在導出子圖里面的邊,兩邊的頂標都變了,不等式的等號仍然成立,仍然在導出子圖里面;原本不在導出子圖里面的邊,它的左端點的頂標減小了,右端點的頂標沒有變,而且由于d的定義,不等式仍然成立,所以他就可能進入了導出子圖里。
? ? 初始時隨便指定一個可行頂標,比如說lx[i]=max{w[i][j]|j是右邊的點},ly[i]=0。然后對每個頂點進行類似Hungary算法的find過程,如果某次find沒有成功,則按照這次find訪問到的點對可行頂標進行上述調整。這樣就可以逐步找到完美匹配了。
? ? 值得注意的一點是,按照上述d的定義去求d的話需要O(N^2)的時間,因為d需要被求O(N^2)次,這就成了算法的瓶頸??梢赃@樣優化:設slack[j]表示右邊的點j的所有不在導出子圖的邊對應的lx[i]+ly[j]-w[i][j]的最小值,在find過程中,若某條邊不在導出子圖中就用它對相應的slack值進行更新。然后求d只要用O(N)的時間找到slack中的最小值就可以了。
? ? 如果是求最小權匹配,只需要把那個不等式反一下就行了。算法需要作出的改變是:lx的初值為所有臨界邊中的最小值,find中t反號。
最大權匹配
/* hdu 2255 奔小康賺大錢 題意:有n個村民n個房子,每個村名對每個房子都有一個報價,求報價和最大的安排最大權匹配 也就是最優匹配這兒有個KM算法的講解 http://blog.csdn.net/hqd_acm/article/details/5829682 http://www.byvoid.com/blog/tag/%E6%9C%80%E5%B0%8F%E6%9D%83%E5%8C%B9%E9%85%8D/zh-hant/ */ #include<stdio.h> #include<string.h> #define N 305 int map[N][N],match[N],lx[N],ly[N],slack[N],visx[N],visy[N]; int n; int hungray(int i)//適應KM算法的 匈牙利算法 {int j;visx[i]=1;for(j=1;j<=n;++j)//{if(visy[j]) continue;if(lx[i]+ly[j]==map[i][j])//若j在相等子圖里 與普通匈牙利算法一樣{visy[j]=1;if(match[j]==-1||hungray(match[j])){match[j]=i;return 1;}}else if(slack[j]>(lx[i]+ly[j]-map[i][j]))//不在相等子圖里 并且可以使slack更小 更新之slack[j]=lx[i]+ly[j]-map[i][j];}return 0; } int km() {memset(match, -1, sizeof(match)); int i,j,d;memset(lx,0,sizeof(lx));memset(ly,0,sizeof(ly));for(i=1;i<=n;i++)//lx取與i連接的邊的最大權for(j=1;j<=n;++j)if(map[i][j]>lx[i]) lx[i]=map[i][j];for(i=1;i<=n;++i)//對每一個點進行匹配{for(j=1;j<=n;j++)//對每一個點來說,需要一直更新lx、ly 直到找到交錯路 slack存的是那個較小的差值,所以求每個點前初始化成最大slack[j]=0x7fffffff;while(1)//當找到交錯路的時候 跳出{memset(visx,0,sizeof(visx));memset(visy,0,sizeof(visy));//初始化訪問標記if(hungray(i))break;d=0x7fffffff;//尋找最小的改變量for(j=1;j<=n;++j)if(!visy[j]&&slack[j]<d) d=slack[j];for(j=1;j<=n;++j)//改變{if(visx[j]) lx[j]-=d;if(visy[j]) ly[j]+=d;else slack[j]-=d;//對于通過這次更新頂標而進入相等子圖的j來說,這不已經沒有意義//但是 對于還沒有進入相等子圖的j來說 他們相連的i已經-d了,所以那個差會減小d 故這里修改slack}}}d=0;for(i=1;i<=n;i++)//所有邊的權都表示在了點上d+=lx[i],d+=ly[i];return d; } int main() {int i,j;while(scanf("%d",&n)!=EOF){for(i=1;i<=n;i++)for(j=1;j<=n;++j)scanf("%d",&map[i][j]);printf("%d\n",km());}return 0; }
最小權匹配
/* poj 2195 Going Home學習了二分圖匹配,所以又用 最小權匹配 些了一邊,比 最小費用最大流 快了一些最小權匹配 與最大權匹配差不多只需要把 邊的權取相反數然后過程跟最大權匹配一樣最后 邊權和 是一個負數 再取相反數即可*/ #include<iostream> #include<cmath> using namespace std; int m,n; struct node//表示人或房子位置 {int x,y; }; struct edge//用鄰接表存邊 {int v,f,next; }e[100000]; node *man,*house;//人和房子的數組 int *match,*lx,*ly,*slack,*visx,*visy,*head,nman;//匹配 x的頂標 y的頂標 優化數組 x訪問標志 y訪問標志 鄰接表頭節點 人的數量 char map[110][110]; int hungray(int i)//匈牙利算法 {int j,v;visx[i]=1;for(j=head[i];j!=-1;j=e[j].next){v=e[j].v;if(visy[v]) continue;if(lx[i]+ly[v]==e[j].f){visy[v]=1;if(match[v]==-1||hungray(match[v])){match[v]=i;return 1;}}else if(slack[v]>lx[i]+ly[v]-e[j].f)slack[v]=lx[i]+ly[v]-e[j].f;}return 0; } int km()//km {memset(match,-1,sizeof(int)*nman);int i,j,d;memset(ly,0,sizeof(int)*nman);for(i=0;i<nman;++i){for(j=0;j<nman;++j)slack[j]=0x7fffffff;while(1){memset(visx,0,sizeof(int)*nman);memset(visy,0,sizeof(int)*nman);if(hungray(i))break;d=0x7fffffff;for(j=0;j<nman;++j)if(!visy[j]&&slack[j]<d) d=slack[j];for(j=0;j<nman;++j){if(visx[j]) lx[j]-=d;if(visy[j]) ly[j]+=d;else slack[j]-=d;}}}d=0;for(i=0;i<nman;++i)d+=lx[i],d+=ly[i];return -d;//取相反數 } int main() {int i,j,yong;while(cin>>n>>m,m+n){nman=0;//初始化yong=0;for(i=0;i<n;++i)//讀數據for(j=0;j<m;++j){cin>>map[i][j];if(map[i][j]=='m')nman++;}match=new int[nman];//申請空間man=new node[nman];//house=new node[nman];//lx=new int[nman];//ly=new int[nman];//slack=new int[nman];//visx=new int[nman];//visy=new int[nman];//head=new int[nman];//int jman=0,jhouse=0;for(i=0;i<n;++i)//統計人和房子的位置for(j=0;j<m;++j){if(map[i][j]=='m'){man[jman].x=i;man[jman++].y=j;}else if(map[i][j]=='H'){house[jhouse].x=i;house[jhouse++].y=j;}}memset(head,-1,sizeof(int)*nman);//計算人和房子的距離 建邊for(i=0;i<nman;++i){int max=-(0x7fffffff);//在計算的同時給lx數組賦值,就是和i相連的邊的最大權for(j=0;j<nman;++j){e[yong].v=j;e[yong].f=-(abs(man[i].x-house[j].x)+abs(man[i].y-house[j].y));//因為求最小權匹配 所以用權的相反數if(max<e[yong].f) max=e[yong].f;//更新maxe[yong].next=head[i];head[i]=yong++;}lx[i]=max;//給lx賦值}cout<<km()<<endl;//計算 輸出delete[] match;//釋放空間delete[] man;delete[] house;delete[] lx;delete[] ly;delete[] slack;delete[] visx;delete[] visy;delete[] head;}return 0; }
總結
以上是生活随笔為你收集整理的KM算法 最优匹配(最大权匹配) hdu 2255 奔小康赚大钱 最小权匹配 poj 2195 Going Home的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 树莓派 远程看鱼眼摄像头
- 下一篇: PMAC应用五-运动学