1. 程式人生 > >P1523 旅行商簡化版

P1523 旅行商簡化版

旅行商問題 ++ turn class 兩種 兩個 n) idea live

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 旅行商簡化版