题目链接 https://vjudge.net/problem/UVA-1347
【题意】
按照x坐标的递增顺序给定平面上的n个点,每个点的x坐标保证不相同(n<=1000),你的任务是设计一条路线,从最左边的点出发,走到最右边的点后返回,要求除了最左端和最右端的两点以外,每个点恰好经过一次,且要让总路程尽量短,两点之间的长度为它们的欧几里德距离。
【思路】
没什么思路,甚至想不到是用dp做,看了紫书上的讲解才明白的。首先是思路转换,将”从左到右再走回来”这么一个过程看成是”两个人同时从最左端出发,沿着两条不同的路径走,最后都到达最右端” 状态的设计也很巧妙,设dp(i,j)表示1~max(i,j)的所有结点都已经走过,且两个人当前的位置是i和j,此时还需要走的最短距离是多少。稍加思考,我们就知道,dp(i,j)=dp(j,i),所以我们规定dp(i,j)中满足i > j
这样一来,不管是哪个人,下一步都可以往i+1,i+2…n这些点走,但是比如说在dp(i,j)的状态下如果某个人直接走到i+2这个点,情况就变成了“1~i和i+2走过,i+1没走过”,这是一种没法表示的状态,怎么办?直接禁止这样的状态转移,也就是说这两个人不管是哪个都只能往i+1走,状态dp(i,j)只能转移到dp(i+1,i)或dp(i+1,i),并且这样是不会漏解的,证明不难,简单的说就是如果第一个人直接从i走到i+2,那么i+1这个点就只能让第二个人来走了,既然这样就先让第二个人走到i+1,最终的结果不会变化。
状态转移方程即为dp[i][j]=min {dp[i+1][j] + dis(i, i+1), dp[i+1][i]+dis(j,i+1)}
边界是dp[n-1][x]=dis(n-1,n)+dis(x,n),就是两个人都往终点走。最后的答案便是dis(1,2)+dp[2][1]
#include
using namespace std;
const double inf = 2e9;
const int maxn = 1050;
int n;
double dp[maxn][maxn];
struct Point {
double x, y;
}p[maxn];
double dis(Point& a, Point& b) {
return sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
}
void d() {
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) dp[i][j] = inf;
}
for (int j = 1; j < n - 1; ++j) {
dp[n - 1][j] = dis(p[n - 1], p[n]) + dis(p[j], p[n]);
}
for (int i = n - 2; i >= 1; --i) {
for (int j = 1; j < i; ++j) {
dp[i][j] = min(dp[i][j], dp[i + 1][j] + dis(p[i], p[i + 1]));
dp[i][j] = min(dp[i][j], dp[i + 1][i] + dis(p[j], p[i + 1]));
}
}
double ans = dis(p[1], p[2]) + dp[2][1];
printf("%.2lf\n", ans);
}
int main() {
while (scanf("%d", &n) == 1) {
for (int i = 1; i <= n; ++i) {
scanf("%lf%lf", &p[i].x, &p[i].y);
}
d();
}
return 0;
}