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;
}

posted @ 2023-10-25 23:46  Landnig_on_Mars  阅读(16)  评论(0编辑  收藏  举报  来源