P1523 旅行商簡化版
P1523 旅行商簡化版
題目背景
歐幾裏德旅行商(Euclidean Traveling Salesman)問題也就是貨郎擔問題一直是困擾全世界數學家、計算機學家的著名問題。現有的算法都沒有辦法在確定型機器上在多項式時間內求出最優解,但是有辦法在多項式時間內求出一個較優解。
為了簡化問題,而且保證能在多項式時間內求出最優解,J.L.Bentley提出了一種叫做bitonic tour的哈密爾頓環遊。它的要求是任意兩點(a,b)之間的相互到達的代價dist(a,b)=dist(b,a)且任意兩點之間可以相互到達,並且環遊的路線只能是從最西端單向到最東端,再單項返回最西端,並且是一個哈密爾頓回路。
題目描述
著名的NPC難題的簡化版本
現在笛卡爾平面上有n(n<=1000)個點,每個點的坐標為(x,y)(-2^31<x,y<2^31,且為整數),任意兩點之間相互到達的代價為這兩點的歐幾裏德距離,現要你編程求出最短bitonic tour。
輸入輸出格式
輸入格式:
第一行一個整數n
接下來n行,每行兩個整數x,y,表示某個點的坐標。
輸入中保證沒有重復的兩點,
保證最西端和最東端都只有一個點。
輸出格式:
一行,即最短回路的長度,保留2位小數。
輸入輸出樣例
輸入樣例#1:7 0 6 1 0 2 3 5 4 6 1 7 5 8 2輸出樣例#1:
25.58
分析:
下面提供兩種方法,刷表法和填表法 1、刷表法 代碼是從前往後刷表【1】題意:旅行商問題,不過要求只能單向走,就是有n個地方,要求從西往東,到最東面的地方,在從東往西返回,經過每個點一次,求最短路徑
【2】分析:由於有了方向的限制,這題不再是NP難題,我們可以假設有兩個人一起從西往東走,走過的點不能重復,這樣就有f[ i ][ j ]表示第一個人走到i,第二個人走到j 的最短路徑,要求i<j,且0到j的點都被經過了,這樣很容易想到,j+1的點不是被第一個人走,就是被第二個人走,所以有轉移方程f[ i ][ j+1]=min{ f[ i ] [ j ]+d[ j ] [ j +1] } f[ j ] [ j+1 ]=min{ f[ i ][ j ]+d[ i ][ j+1 ] },第一個轉移方程很容易理解,第二個方程可以這麽理解,兩個人可以指前面一個人,和後面一個人,當後面的人走到前面,當然就對換過來了,不影響結果
【3】最後,預處理f[ 0 ][ 1]還有掃描 一遍答案就行了,這題算是一類DP吧,思路挺有啟發性的
1 #include <cmath> 2 #include <cstdio> 3 #include <iostream> 4 #include <cstring> 5 #include <algorithm> 6 using namespace std; 7 const int mm=1111; 8 struct data 9 { 10 double x,y; 11 }g[mm]; 12 double d[mm][mm],f[mm][mm]; 13 int i,j,k,n; 14 bool cmp(const data &a,const data &b) 15 { 16 return a.x<b.x; 17 } 18 double mdis(const data &a, const data &b) 19 { 20 return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); 21 } 22 int main() 23 { 24 scanf("%d",&n); 25 for(i=0;i<n;++i) 26 scanf("%lf%lf",&g[i].x,&g[i].y); 27 sort(g,g+n,cmp); 28 for(i=0;i<n;++i) 29 for(j=i+1;j<n;++j) 30 { 31 d[i][j]=mdis(g[i],g[j]); 32 f[i][j]=1e30; 33 } 34 f[0][1]=d[0][1]; 35 for(i=0;i<n;++i) 36 for(j=i+1;j<n;++j) 37 { 38 f[i][j+1]=min(f[i][j+1],f[i][j]+d[j][j+1]); 39 f[j][j+1]=min(f[j][j+1],f[i][j]+d[i][j+1]); 40 } 41 double ans=1e30; 42 for(j=0;j<n-1;++j) 43 ans=min(ans,f[j][n-1]+d[j][n-1]); 44 printf("%.2lf\n",ans); 45 return 0; 46 }
2、填表法
代碼是從後往前填表
遍歷一整個圖, 從開始位置走到末尾,再走回開始位置,並且不能重復;
由於題目並沒有對輸入數據按X從小到大排序,所以先排序,然後預處理出每兩個點之間的歐幾裏得距離。
f(i,j)表示 從 1->max(i,j) 已經全部遍歷切沒有重復的最短路;
那麽可知F(i , j) = F(j , i); 於是就規定 i > j;
如果 i = n - 1; 那麽就強行將j跳到末尾位置;f(i, j) = dis(i, n) + dis(j, n);
其余可知 f(i,j) = max ( f(i+1, j) + dis(i, i +1), f(i+1, i) + dis(j, i+1)) 原本應寫成f(i, i+1), 但由於規定所以寫成這樣;
最終狀態為f(2, 1);
接著就是naive的輸出啦;
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cmath> 5 #include<cstring> 6 typedef long long Lovelive; 7 using namespace std; 8 9 const int maxn = 1000 + 10; 10 int n; 11 struct Node { 12 double x, y; 13 }node[maxn]; 14 double dis[maxn][maxn], f[maxn][maxn]; 15 16 bool cmp(Node a, Node b) { 17 return a.x < b.x; 18 } 19 20 int main() { 21 scanf("%d",&n); 22 for(int i = 1; i <= n; i++) scanf("%lf%lf",&node[i].x,&node[i].y); 23 sort(node+1, node+n+1, cmp); 24 for(int i = 1; i <= n; i++) 25 for(int j = 1; j <= n; j++) 26 dis[i][j] = sqrt((node[i].x-node[j].x)*(node[i].x-node[j].x) + (node[i].y-node[j].y)*(node[i].y-node[j].y)); 27 28 for(int i = n-1; i >= 2; i--) 29 for(int j = 1; j < i; j++) 30 if(i == n-1) f[i][j] = dis[i][n] + dis[j][n]; 31 else f[i][j] = min (f[i+1][j] + dis[i][i+1], f[i+1][i] + dis[j][i+1]); 32 printf("%.2lf",f[2][1] + dis[1][2]); 33 return 0; 34 }
P1523 旅行商簡化版