Traveling Salesman Problem
Description: Time Limit: 4sec Memory Limit:256MB
有編號1到N的N個城市,問從1號城市出發,遍歷完所有的城市並最后停留在N號城市的最短路徑長度。
Input:
第一行整數 T :T組數據 (T<=20)
每個case 讀入一個N( 2 <= N <= 20),接着輸入N行,第i行有兩個整數 xi , yi 表示第 i 個城市坐標軸上的坐標 。
Output:
每個case輸出一個浮點數表示最短路徑。四舍五入保留兩位小數。
Sample Input:
1 4 0 0 1 0 1 1 0 1
Sample Output:
3.41
經典難題!數據開到這么小就知道沒有那么簡單的復雜度了,一般的算法,即爆搜,復雜度為 o( n! ) ,我們這里采用的動態規划算法,
算法復雜度已經從階乘級降到了o( ( n^2 )*( 2^n ) ) (看起來也是相當恐怖的,不過像這種經典難題這種復雜度對我來說已經不錯了)。
開始說說思路,一開始馬上想到的必然是搜索,搜索必然超時,於是某大神直接告訴我——記憶化搜索,記憶化搜索能做的動規就能做,寫遞歸太麻煩了於是動規!
題目中起點終點確定,我們可以考慮用一個二維dp數組來保存一個狀態——dp[i]{V}表示從結點0到結點 i 途經V中所有節點的最短路徑長(這里的V是一個集合)
於是狀態轉移方程可以為:dp[i]{V}=min( dp[i]{V} , dist[i][j]+dp[j]{V-{j}} ) (j 屬於 V)
大思路定好了,我們來考慮細節部分,主要有以下部分:
1)建圖等等:結構體point,距離函數dist;
2)集合V的表示:二進制數,即010表示三個數的集合第二個有,其余無;
3)dp過程的范圍:1 ~ n-1 (最大可能為 1 ~ 18 );
於是我們可以敲代碼啦!
#include <bits/stdc++.h> const double INF=10e7; using namespace std; int T,n,cnt; double a[25][25],dp[25][1100000]; struct point{ //結點結構體 int x,y; }pt[25]; double d(point a,point b){ //結點間距離 return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } int main() { scanf("%d",&T); while(T--) { cnt=1; scanf("%d",&n); for(int i=2;i<n;i++) cnt<<=1; //組合數(除起點終點外) for(int i=0;i<n;i++) //輸入 scanf("%d %d",&pt[i].x,&pt[i].y); for(int i=0;i<n;i++) //建邊 for(int j=0;j<n;j++) a[i][j]=d(pt[i],pt[j]); for(int i=0;i<n;i++) //初始化 for(int j=0;j<cnt;j++) dp[i][j]=INF; for(int i=0;i<n;i++) //起點確定,定下初始條件 dp[i][0]=a[i][0]; for(int i=1;i<cnt;i++) //從有元素考慮起 for(int j=1;j<n-1;j++) { for(int k=1;k<n-1;k++) { if((1<<k-1)&i) //k is in the set dp[j][i]=min(dp[j][i],a[j][k]+dp[k][i-(1<<k-1)]); //狀態轉移方程 } } double ans=INF; for(int i=1;i<n;i++) ans=min(ans,dp[i][cnt-1]+a[i][n-1]); printf("%.2lf\n",ans); } return 0; }
之前動態規划也是做了不少的基礎例題,這道題算是動態規划的第一次成功應用,還是蠻開心的,軟創得加油啊!