//放假前一天考这么BT的DP…
背景 Background
欧几里德旅行商(Euclidean Traveling Salesman)问题也就是货郎担问题一直是困扰全世界数学家、计算机学家的著名问题。现有的算法都没有办法在确定型机器上在多项式时间内求出最优解,但是有办法在多项式时间内求出一个较优解。
为了简化问题,而且保证能在多项式时间内求出最优解,J.L.Bentley提出了一种叫做bitonic tour的哈密尔顿环游。它的要求是任意两点 ( a , b ) (a,b) (a,b)之间的相互到达的代价 d i s t ( a , b ) = d i s t ( b , a ) dist(a,b)=dist(b,a) dist(a,b)=dist(b,a)且任意两点之间可以相互到达,并且环游的路线只能是从最西端单向到最东端,再单项返回最西端,并且是一个哈密尔顿回路。
//Tip : 哈密尔顿回路 : 通过图中每个顶点一次且仅一次的回路。
题目描述 Description
著名的NPC难题的简化版本
现在笛卡尔平面上有 n ( n ≤ 1000 ) n(n≤1000) n(n≤1000)个点,每个点的坐标为 ( x , y ) (x,y) (x,y)( − 2 31 < x , y < 2 31 -2^{31}<x,y<2^{31} −231<x,y<231,且为整数),任意两点之间相互到达的代价为这两点的欧几里德距离,现要你编程求出最短bitonic tour 。
//还是Tip : 笛卡尔平面 : 平面直角坐标系
输入 Input
第一行一个整数 n n n
接下来 n n n行,每行两个整数 x , y x,y x,y,表示某个点的坐标。
输入中保证没有重复的两点,
保证最西端和最东端都只有一个点。
//第三个Tip : 数据同时保证任意两点的横坐标不同
输出 Output
一行,即最短回路的长度,保留 2 2 2位小数。
样例输入 Sample Input
7
0 6
1 0
2 3
5 4
6 1
7 5
8 2
样例输出 Sample Output
25.58
限制 Limits
数据范围见题目
Time Limit : 1 s 1s 1s & Memory Limit : 128 M B 128MB 128MB
来源 Source
《算法导论(第二版)》 15-1
考试0分…
看到题之后是不是很想打个Floyed?
n
3
n^3
n3过不去哦…
那么就想想DP
一个人只能单调从起点走到终点,再由终点走到起点,题里说两点间距离
d
i
s
(
a
,
b
)
=
d
i
s
(
b
,
a
)
dis(a,b)=dis(b,a)
dis(a,b)=dis(b,a),所以可以把一个人拆成两个人走这个路程
那么**
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]就可以表示当第一个人走到i点,第二个人走到j点所取得的最短路程**
接下来就得让两个人走起来
但是走之前还得注意一个问题,
d
p
[
i
]
[
j
]
=
d
p
[
j
]
[
i
]
dp[i][j]=dp[j][i]
dp[i][j]=dp[j][i]
why?
第一个人走到
i
i
i点,第二个人走到
j
j
j点和第二个人走到
i
i
i点,第一个人走到
j
j
j点的最短距离如果不等,那么求得的
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]和
d
p
[
j
]
[
i
]
dp[j][i]
dp[j][i]一定有一个不是最短距离(…)
好了,那么用什么让他走起来
相邻的两个点之间一定没有别的点(Tip3所说,大家自行理解)
所以下一个要走的点是什么东西
+
1
+1
+1
是什么东西呢?
现在两人位置的横坐标的最大值
+
1
+1
+1(假如第一个人到
i
i
i位置,第二个人到
j
j
j位置,下一个位置就是
m
a
x
(
i
,
j
)
+
1
max(i,j)+1
max(i,j)+1)
因为最大的横坐标之前已经处理完了,可以用
i
i
i位置到
j
j
j位置的距离…
上代码
#include <cmath>
#include <cstdio>
#include <algorithm>
#define MAXN 1010
#define INF 1e50
using namespace std;
struct point
{
double x;
double y;
};
double get_dis(point a,point b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
bool cmp(point a,point b)
{
return a.x<b.x;
}
point a[MAXN];
double dp[MAXN][MAXN];
int n;
double mymin(double a,double b)
{
return a-b<0?a:b;
}
int myminint(int a,int b)
{
return a<b?a:b;
}
int mymax(int a,int b)
{
return a>b?a:b;
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%lf %lf",&a[i].x,&a[i].y);
sort(a+1,a+n+1,cmp);//按x升序排序
for (int i=1;i<=n;i++)//初值
for (int j=1;j<=n;j++)
dp[i][j]=INF;
dp[1][1]=0.0;//第一个,第二个人都在1点是初始状态,距离为0
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
{
int nxt=myminint(n,mymax(i,j)+1);//nxt点为下一个要考察的点
//dp[i及之前][j及之前]已经处理完毕,由于对称性,考察最大位置+1即可,加min防治越界
dp[nxt][j]=mymin(dp[nxt][j],dp[i][j]+get_dis(a[i],a[nxt]));//让第一个人走到nxt点
dp[i][nxt]=mymin(dp[i][nxt],dp[i][j]+get_dis(a[j],a[nxt]));//让第二个人走到nxt点
}
printf("%.2lf",dp[n][n]);
return 0;
}
DP瞎搞#1