Description
欧几里德旅行商(Euclidean Traveling Salesman)问题也就是货郎担问题一直是困扰全世界数学家、计算机学家的著名问题。现有的算法都没有办法在确定型机器上在多项式时间内求出最优解,但是有办法在多项式时间内求出一个较优解。
为了简化问题,而且保证能在多项式时间内求出最优解,J.L.Bentley提出了一种叫做bitonic tour(双调旅程)的哈密尔顿环游来解决问题。
它的要求是任意两点(a,b)之间的相互到达的代价dist(a,b)=dist(b,a)且任意两点之间可以相互到达,并且环游的路线只能是从最西端单向到最东端,再从最东端返回最西端,并且是一个哈密尔顿回路。图b显示了同样的7个点的问题的最短双调路线,在这种情况下,多项式的时间的算法是有可能的。
现在笛卡尔平面上有n(n<=1000)个点,每个点的坐标为(x,y)(-2^31< x,y< 2^31,且为整数),任意两点之间相互到达的代价为这两点的欧几里德距离,现要你编程求出最短bitonic tour。
Input
包含若干组数据,每组数据格式如下:
第一行一个整数n
接下来n行,每行两个整数x,y,表示某个点的坐标。
输入中保证没有重复的两点, 保证最西端和最东端都只有一个点。
Output
对于每组数据,输出一行,即最短回路的长度,保留2位小数。
Input sample
7
0 6
1 0
2 3
5 4
6 1
7 5
8 2
3
1 1
2 3
3 1
4
1 1
2 3
3 1
4 2
Output sample
25.58
6.47
7.89
————————————————分割の线————————————————
分析
首先是预处理,d[i][j]表示从i到j的距离。 因为是从1点出发到达n点再回到1点,所以可以看作是两个旅行商均从1点出发到达n点,期间两人不经过同一点。
如此可以定义f[i][j]表示一个旅行商到达i点,另一个旅行商到达j点,从1到max(i,j)点都已经走过后的最小值。因为f[i][j]==f[j][i],所以可以规定:
i < j,f[i][j]表示一个旅行商到达i点,另一个旅行商到达j点,且从1到j均已走过的最小值。
因为第j+1个点,会有两条线路中的其中一条经过,所以可以得出状态更新方程(本题使用刷表法):
f[i][j+1]=max(f[i][j+1],f[i][j]+d[j][j+1]);
f[j][j+1]=max(f[j][j+1],f[i][j]+d[i][j+1]);
求最短路的答案
ans=min(ans,f[i][n]+d[i][n]);
完整代码如下:
#include <cmath>
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=1111;
struct data
{
double x,y;
}g[maxn];//储存每个点的横纵坐标
double d[maxn][maxn],f[maxn][maxn];
int i,j,k,n;
bool cmp(data a,data b)
{
return a.x<b.x;
}//按照横坐标排序,貌似有些Oj的横坐标是有序的
double mdis(data a,data b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}//计算长度
int main()
{
while(scanf("%d",&n)!=EOF)//判断是否文件结束
{
for(i=1;i<=n;i++)
scanf("%lf%lf",&g[i].x,&g[i].y);
sort(g+1,g+n+1,cmp);//读入坐标并排序
for(i=1;i<=n;i++)
for(j=i+1;j<=n;j++)
{
d[i][j]=mdis(g[i],g[j]);//计算长度
f[i][j]=1e30;//给f赋初值
}
f[1][2]=d[1][2];
for(i=1;i<=n;i++)
for(j=i+1;j<=n;j++)
{
f[i][j+1]=min(f[i][j+1],f[i][j]+d[j][j+1]);
f[j][j+1]=min(f[j][j+1],f[i][j]+d[i][j+1]);
}//更新i,j之后的状态
double ans=1e30;
for(i=1;i<n;i++)
ans=min(ans,f[i][n]+d[i][n]);//扫描最小值
printf("%.2lf\n",ans);
}
return 0;
}