1. 程式人生 > >HDU 3488 Tour (最大權完美匹配)【KM演算法】

HDU 3488 Tour (最大權完美匹配)【KM演算法】

<題目連結>

題目大意:
給出n個點m條單向邊邊以及經過每條邊的費用,讓你求出走過一個哈密頓環(除起點外,每個點只能走一次)的最小費用。題目保證至少存在一個環滿足條件。

解題分析

因為要求包含所有點一次的環,我們不難發現,這個環中的每個點的出度和入度均為1,所以我們不妨將每個點進行拆點,將所有點的出度和入度分為兩部分。因為該環需要包括所有的點,並且題目需要求該環的最小權值,相當於該帶權二分圖的每個點都需要被覆蓋到,由於本題就轉化為求該二分圖的最優完美匹配問題。二分圖的最優匹配問題求解,我們會想到KM演算法,但是KM是求最大權完美匹配,所以我們對每個邊的權值全部取反,這時候求出的最大權值(該權值<0)的相反數就是最小權值的完美匹配了。

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 const int N =205;
 7 #define mem(a,b) memset(a,b,sizeof(a))
 8 #define rep(i,s,t) for(int i=s;i<=t;i++)
 9 #define INF 0x3f3f3f3f
10 int n,linker[N],w[N][N],lx[N],ly[N],slack[N];
11 int visx[N],visy[N],nx,ny; 12 bool DFS(int x){ 13 visx[x]=1; 14 rep(y,1,n){ 15 if(visy[y]==1)continue; //每次只常識匹配一次y,相當於匈牙利中的vis[] 16 int tmp=lx[x]+ly[y]-w[x][y]; //x,y期望值之和與x,y之間的權值的差值 17 if(!tmp){ //x,y之間期望值==他們之間權值時符合要求 18 visy[y]=1; 19 if
(linker[y]==-1||DFS(linker[y])){ //y沒有歸屬者,或者y的原始歸屬者能夠找到其他歸屬者 20 linker[y]=x; 21 return true; 22 } 23 }else slack[y]=min(slack[y],tmp); 24 } 25 return false; 26 } 27 int KM(){ 28 mem(linker,-1);mem(ly,0); //初始化,y的期望值為0 29 rep(i,1,nx){ //初始化lx[]陣列 30 lx[i]=-INF; 31 for(int j=1;j<=ny;j++){ 32 lx[i]=max(lx[i],w[i][j]); //lx為x的期望值,lx初始化為與它關聯邊中最大的 33 } 34 } 35 //為每一個x嘗試解決歸屬問題 36 rep(x,1,n){ 37 rep(i,1,n)slack[i]=INF; 38 while(true){ 39 mem(visx,0);mem(visy,0); 40 if(DFS(x))break;//若成功(找到了增廣軌),則該點增廣完成,進入下一個點的增廣 41 //若失敗(沒有找到增廣軌),則需要改變一些點的標號,使得圖中可行邊的數量增加。 42 //方法為:將所有在增廣軌中(就是在增廣過程中遍歷到)的X方點的標號全部減去一個常數d, 43 //所有在增廣軌中的Y方點的標號全部加上一個常數d 44 int d=INF; 45 rep(i,1,ny)if(!visy[i])d=min(d,slack[i]); //d為沒有匹配到的y的slack中的最小值 46 rep(i,1,nx)if(visx[i])lx[i]-=d; 47 rep(i,1,ny) 48 if(visy[i])ly[i]+=d; 49 else slack[i]-=d; //修改頂標後,要把所有不在交錯樹中的Y頂點的slack值都減去d 50 } 51 } 52 int res=0; 53 rep(i,1,ny){ 54 if(linker[i]!=-1) 55 res+=w[linker[i]][i]; 56 } 57 return res; 58 } 59 /*-- 以上為KM演算法模板 --*/ 60 int main(){ 61 int T,m,u,v,c;scanf("%d",&T); 62 while(T--){ 63 scanf("%d%d",&n,&m); 64 rep(i,1,n) rep(j,1,n){ 65 w[i][j]=-INF; 66 } 67 //將每個點進行拆點,分成出度(x部分)和入度(y部分)兩部分來處理 68 nx=ny=n; 69 rep(i,1,m){ 70 scanf("%d%d%d",&u,&v,&c); 71 if(w[u][v]<-c) //因為要求最小的權值,而KM演算法是求最大的權值,所以這裡將所有邊的權值取反,這樣用KM算出的最大值的相反數就是最小值了 72 w[u][v]=-c; //去重邊,取權值最小的邊 73 } 74 printf("%d\n",-1*KM()); //對求出的最大值取反即可 75 } 76 }

 

 

2018-11-18