P1523 旅行商简化版, 路径dp
P1523 旅行商简化版 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这是一道很有启发意义,代表性的难题
题目背景
欧几里德旅行商(Euclidean Traveling Salesman)问题也就是货郎担问题一直是困扰全世界数学家、计算机学家的著名问题。现有的算法都没有办法在确定型机器上在多项式时间内求出最优解,但是有办法在多项式时间内求出一个较优解。
为了简化问题,而且保证能在多项式时间内求出最优解,J.L.Bentley 提出了一种叫做 bitonic tour 的哈密尔顿环游。它的要求是任意两点 (a,b) 之间的相互到达的代价 dist(a,b)=dist(b,a) 且任意两点之间可以相互到达,并且环游的路线只能是从最西端单向到最东端,再单项返回最西端,并且是一个哈密尔顿回路。
题目描述
本题为著名的 NPC 难题的简化版本。
现在笛卡尔平面上有 n(n≤1000) 个点,每个点的坐标为(x,y),(−231<x,y<231,且为整数),任意两点之间相互到达的代价为这两点的欧几里德距离,现要你编程求出最短 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
说明/提示
题目来源
《算法导论(第二版)》 15-1
解析:
题目意思:给定一堆点,求从左到右两天不相交的路径(起点和终点除外)的最小权值和。
将点按横坐标排序;将集合划分为 f[i][j] 两个不从不漏的子集,表示:第一条路经以第 i 个点为终点,第二条路径以第 j 个点为终点,且除起点外严格不相交的两条路经的权值的最小值。
由于 f[i][j]==f[j][i],所以我们让 i>j;
根据划分的含义,状态 f[i][j] 可由一下状态转移而来:
当 i=j+1 时:f[j][k] (k=1,……,j-1);
当 i>j+1 时:f[i-1][j];
我是用的是顺推的方式,这里介绍一个更简洁的逆推的方法:题解 P1523 【旅行商简化版】 - 梦想家的憩息所 - 洛谷博客 (luogu.com.cn)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 1e3 + 5;
const double inf = 1e50;
int n;
struct node {
double x, y;
}arr[N];
double f[N][N];
int cmp(const struct node& a, const struct node& b) {
if (a.x == b.x)
return a.y < b.y;
return a.x < b.x;
}
double dist(int a, int b) {
double ret = fabs(arr[a].x - arr[b].x) * fabs(arr[a].x - arr[b].x);
ret += fabs(arr[a].y - arr[b].y) * fabs(arr[a].y - arr[b].y);
return sqrt(ret);
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%lf%lf", &arr[i].x, &arr[i].y);
}
sort(arr + 1, arr + 1 + n, cmp);
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n; j++)
f[i][j] = inf;
}
f[2][1] = dist(1, 2);
for (int i = 3; i <= n; i++) {
for (int j = 1; j <=i-1; j++) {
if (i == j + 1) {
for (int k = 1; k <=j-1; k++) {
f[i][j] = min(f[i][j], f[j][k] + dist(k, i));
}
}
else {
f[i][j] = min(f[i][j], f[i - 1][j] + dist(i, i - 1));
}
}
}
double ans = inf;
for (int i = 1; i <= n; i++) {
ans = min(f[n][i] + dist(n, i), ans);
}
printf("%.2lf", ans);
return 0;
}