欧几里得旅行商问题是对平面上给定的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.
我们需要找到 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
下面以2014百度之星Disk Schedule为例子,使用TSP算法
Problem Description
有很多从磁盘读取数据的需求,包括顺序读取、随机读取。为了提高效率,需要人为安排磁盘读取。然而,在现实中,这种做法很复杂。我们考虑一个相对简单的场景。
磁盘有许多轨道,每个轨道有许多扇区,用于存储数据。当我们想在特定扇区来读取数据时,磁头需要跳转到特定的轨道、具体扇区进行读取操作。为了简单,我们假设磁头可以在某个轨道顺时针或逆时针匀速旋转,旋转一周的时间是360个单位时间。磁头也可以随意移动到某个轨道进行读取,每跳转到一个相邻轨道的时间为400个单位时间,跳转前后磁头所在扇区位置不变。一次读取数据的时间为10个单位时间,读取前后磁头所在的扇区位置不变。磁头同时只能做一件事:跳转轨道,旋转或读取。
现在,需要在磁盘读取一组数据,假设每个轨道至多有一个读取请求,这个读取的扇区是轨道上分布在 0到359内的一个整数点扇区,即轨道的某个360等分点。磁头的起始点在0轨道0扇区,此时没有数据读取。在完成所有读取后,磁头需要回到0轨道0扇区的始点位置。请问完成给定的读取所需的最小时间。Input
输入的第一行包含一个整数M(0<M<=100),表示测试数据的组数。
对于每组测试数据,第一行包含一个整数N(0<N<=1000),表示要读取的数据的数量。之后每行包含两个整数T和S(0<T<=1000,0<= S<360),表示每个数据的磁道和扇区,磁道是按升序排列,并且没有重复。Output
对于每组测试数据,输出一个整数,表示完成全部读取所需的时间。
Sample Input
3
1
1 10
3
1 20
3 30
5 10
2
1 10
2 11
Sample Output
830
4090
1642
我们把要读取的数据的位置定义为 坐标(T,S),化成坐标后,我们需要对T的大小进行排序。
然后按照TSP的思路进行求解即刻。
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
#define INF 88888888888
struct Data {
int T, S;
};
int b[1001][1001];
Data p[1001];
int N;
int distance(Data x, Data y) {
int dis;
dis = abs(x.T - y.T) * 400;
int tmp = abs(x.S - y.S);
if (tmp > 180)dis += 360 - tmp;
else dis += tmp;
return dis;
}
bool cmp(Data x, Data y) {
return x.T < y.T;
}
void DP() {
int i, j, k;
b[0][0] = 0;
b[1][0] = distance(p[1], p[0]);
for (i = 2; i <= N; i++) {
for (j = 0; j < i; j++) {
if (i != j + 1)
b[i][j] = b[i - 1][j] + distance(p[i - 1], p[i]);
else {
b[i][j] = INF;
int temp;
for (k = 0; k < j; k++) {
temp = b[i - 1][k] + distance(p[k], p[i]);
if (temp < b[i][j])
b[i][j] = temp;
}
}
}
}
//求b[n][n]
b[N][N] = INF;
int temp;
for (k = 0; k < N; k++) {
temp = b[N][k] + distance(p[N], p[k]);
if (temp < b[N][N])
b[N][N] = temp;
}
}
int main() {
int M;
cin >> M;
while (M--) {
cin >> N;
p[0].T = 0;
p[0].S = 0;
for (int i = 1; i <= N; i++) {
cin >> p[i].T >> p[i].S;
}
sort(p, p + N + 1, cmp);
DP();
cout << b[N][N] + N * 10 << endl;
}
return 0;
}