題目連接:http://acm.hdu.edu.cn/showproblem.php?pid=1217
這道題是tsp板子題,不會做硬鋼了兩天,看了題解學了tsp,現在有點似懂非懂,簡單記錄一下.
歐幾里得旅行商問題是對平面上給定的n個點確定一條連接各點的最短閉合旅程的問題,下圖a給出了7個點問題的解。這個問題的一般形式是NP完全的,故其解需要多於多項式的時間。
(a) (b)
J.L.Bentley建議通過只考慮雙調旅程來簡化問題,這種旅程即為從最左點開始,嚴格地從左到右直至最右點,然后嚴格地從右到左直至出發點。b顯示了同樣7個點問題的最短雙調路線。在這種情況下,多項式時間的算法是可能的。
描述一個確定最優雙調路線的O(n^2)時間的算法,可以假設任何兩點的x坐標都不相同。
將各個節點從左到右排序,編號為1,2,3,.....,n。對於任意的i和j(其中1<=i,j<=n)。
現在有兩條路徑A、B都是從點1出發,A走的路徑為1~ i ,B走的路徑為1~ j ,
即A、B有公共的起點,但途中沒有交叉點(即終點之前不存在 i=j ),終點可能重合(i=j),也可能不重合(i≠j),這取決與我們要求的問題。
令s=max(i,j),則從1到s所有的點一定在路徑A或者路徑B上,不會有遺漏的點.
對於特定的 i 和 j ,路徑A、B存在多種可能的走法,其中比有一種2條路徑的和最小的走法,我們把這2條路徑的和記為b[i,j];當i=j時,b[i,j]表示了從1到 i 的雙調TSP的解;當i=j=n時 b[i,j] 就表示了整個問題的最終解法。
我們可以采用DP(動態規划)求解
上圖表示對b[i,j]的遞推情況,開始時,顯然b[i,j]=0,而i=0或j=0也可以直接確定b[i,j]的值;
基於對稱的考慮,表的左下半部和右上半部的數值將完全相同,所以在生成表的時候可以不用考慮下半部分
如何求遞推?分一下幾種情況
① i > j (即圖的右上部分)
已知b[i,j] ,求b[i+1,j],只要將A直接延長到 i+1就行。
即 b[i+1,j] = b[i,j] + distance(i,i+1)
② i = j (對角線部分)
假設已知b[i,i],求b[i+1,i],可以想象,此時AB兩條路徑在終點 i 相交,因為現在我們要求A的終點為i+1,所以不得不把相交的AB在i點拆開。
事情沒那么簡單,拆開后,我們需要在路徑A找任意點u(0<=u<=i),使點u連接點i+1.
由於b[ u , j ] 是A路徑到u,B路徑到j,經過max(u,j)內所有點,所以b[ u , j ]+distance( i , u ) = b[ i , j ]
我們需要找到 min{ b[u,j] + distance(u, i+1)},即遍歷 b[1..i, j],剛好這是圖的左下部分,因為這個部分是和右上部分對稱的,所以可以直接利用右上部分的數據。
b[i+1,j] = min{ B[u,i] + w(u,i+1) }
③ 確定b[n,n],即確定最終的解。
前面 ①②部分已經可以把所有的點都走一次了,現在我們最后需要做的是,把AB路徑的頭連接起來。
那怎么連接才能讓AB路徑加起來最短呢?
前面我們說過: s=max(i,j),則從1到s所有的點一定在路徑A或者路徑B上,不會有遺漏的點
通過①②我們已經求出 1~s 點的所有數據。
也就是我們可以通過 min{ b[n,k] + distance(n,k)} 其中 1<=k<n 求出b[n,n];
b[n,n] = min{ b[n,k] + distance(n,k)}
總結一下:
b[i,j] = b[i-1,j] + distance(i-1,i) (j+1 < i ,藍色部分)
b[i,j] = min{ B[u,i] + w(u,i+1) } (j+1 = i ,紅色部分)
b[n,n] = min{ b[n,k] + distance(n,k)} 0<=k<n
轉自大佬博客:https://blog.csdn.net/qq742762377/article/details/85050040
附加:想到一個問題,有人問我,我們既然可以從(0,0)遞推到(1,0),遞推到(2,0).......到(n,0) 為什么直接從(2,0)推到(2,1)呢?
解釋一下,根據我們的定義在(2,0)的時候,一號點是已經走過了,所以如果我們要從(2,0)推(2,1)的話就會發生這樣的事情
這個代表(2,0),推到到(2,1)的時候就變成了這樣
這樣肯定不對啊,因為兩條路在1號點就相交了所以(2,1)只能根據(1,0)推出來
題目ac代碼:
#include<bits/stdc++.h> using namespace std; #define INT_MAX 0x73f3f3f typedef struct W_W{ int a; int b; }miao; miao x[1010]; double weight[1010][1010]; double dp[101][1010]; double jl(int a,int b){ return sqrt((x[a].a-x[b].a)*(x[a].a-x[b].a)*1.0+(x[a].b-x[b].b)*(x[a].b-x[b].b)*1.0); } bool cmp(miao a,miao b){ return a.a<b.a; } int main() { int n; while(~scanf("%d",&n)){ for(int i=0;i<n;i++){ scanf("%d %d",&x[i].a,&x[i].b); } sort(x,x+n,cmp); for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ dp[i][j]=-1.0; weight[i][j]=0.0; } } for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ if(i==j){ weight[i][j]=0; } else{ weight[i][j]=jl(i,j); } } } dp[0][0]=0; dp[1][0]=weight[1][0]; for(int i=2;i<n;i++){ for(int j=0;j<i;j++){ if(i!=j+1){ dp[i][j]=dp[i-1][j]+weight[i][i-1]; } else{ double minn=INT_MAX; for(int k=0;k<j;k++){ minn=min(minn,dp[j][k]+weight[i][k]); } dp[i][j]=minn; } } } double ans=INT_MAX; //printf("%d\n",dp[1][1]); // for(int i=0;i<n;i++){ // for(int j=0;j<n;j++){ // printf("%.2lf ",dp[i][j]); // } // printf("\n"); // } for(int i=0;i<n-1;i++){ ans=min(dp[n-1][i]+weight[n-1][i],ans); } printf("%.2f\n",ans); } return 0; }