P1523 旅行商简化版

Posted 范仁义

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 旅行商简化版的主要内容,如果未能解决你的问题,请参考以下文章

旅行商问题 状压DP

TSP 旅行商

算法学习双调欧几里得旅行商问题(动态规划)(转)

为啥我的 C 代码片段不起作用?简化版可以。为 unsigned long long 传递不带 VA_ARGS 的 args

人工智能 --- 遗传算法解决TSP问题(JAVA版)

当我向旅行商问题添加更多顶点时,为啥我没有得到结果?