算法竞赛入门经典例9-2 UVa1347 Tour

题目描述

    给定平面上n(n<=1000)个点的坐标(按照x的递增顺序给出。各点x坐标不同,且均为正整数)。现在从最左边出发,走到最右边的点后再返回,要求除了最左边和最右边的点外,每个点恰好经过一次,并让路径总长度最短。两点的长度为它们的欧几里得距离。

题目分析

    这道题思考起来比较复杂,原因在于难以找到一个容易表示的状态。接下我们一步一步分析,如何找出一个优秀的状态模型来描述这道题目。

    根据紫书中的思路:从左到右再到左不易思考,我们可将其想成两个人同时沿不同的路径从左到右,且除了最左和最右两个点之外,均无其他点被访问两次。因此可设dp(i,j)为第一个人在第i个点,第二个人在第j个点,此时两人到终点还需要走多长的距离。现在设dist(i,j)为第i点到第j点的欧几里得距离。

    那么此时状态如何转移呢?经过思考,发现在这种情况下,我们这样做好像无法保证两个人不会走到相同的点。为什么呢?如果第一个人原来在i,第二个人在i-1,即现在的两人的位置是(i,i-1),如果不加约束的话,第二个人能够走到的点可以是i,i+1,i+2...如果第二个人从i-1走到i,那么两人就走到了相同的点,与题意相悖。

    那么如何保证两个人不会走到相同的点上呢?紫书给出了一种思路:定义dp(i,j)表示1~max(i,j)上的点全部走过,且两个人的状态分别是i和j。

    易知dp(i,j)=dp(j,i),所以我们现在可规定状态中的i>j。不管哪个人,他的下一步都必须是i+1,i+2,i+3...,不会造成相同的点被走过两次。但是如果第一个人走到了i+2,那么情况变成了1~i和i+2都走过而i+1没走过,按照上面的规则,下一步必须大于max(i,j)即i+2。这样便造成了i+1没有走过。

    那怎么办呢?我们禁止这样的决策,即只允许其中一个人走到i+1,而不能走到i+2,i+3...。换句话说,能转移的状态只有两种了:dp(i+1,j)和dp(i,i+1)。

    随即我们可以得到状态方程:dp(i,j)=min(dp(i+1,j)+dist(i,i+1),dp(i,i+1)+dist(j,i+1))。

    得到状态转移方程后,现在我们来寻找边界状态。什么时候是边界状态呢?我们来回顾一下我们对dp(i,j)的定义:dp(i,j)表示1~max(i,j)上的点全部走过,且两个人的状态分别是i和j,此时还需要走多少距离。经过分析可以知道,两人均要到达n,且有且只有一个人要达到n-1。这就是走完行程的上一步的状态:有且只有一个人在n-1处。

    根据我们之前定义i>j,那么dp(n-1,j)=dist(n-1,n)+dist(j,n)。

    其中dist(n-1,n)表示第一个人必须在n-1这个位置,差最后一步,即n-1到n这一段。dp(j,n)表示第二人在j这个位置,他的下一步只能大于max(i,j)即max(n-1,j)=n。

    这就是紫书上对这道题的全部思路,下面上AC代码。

#include <iostream>
#include <algorithm>
#include "memory.h"
#include <cmath>
using namespace std;
const int maxn=1000+10;
const int INF=1<<30;
int n;
struct Node{
    int x,y;
    bool operator<(const Node&t ){
        return x<t.x;
    }
};
Node node[maxn];
double dp[maxn][maxn];
double dist(const Node&s,const Node&t){
    double x=s.x-t.x;
    double y=s.y-t.y;
    return sqrt(x*x+y*y);
}
int main(int argc, const char * argv[]) {
    cin>>n;
    for(int i=1;i<=n;i++)cin>>node[i].x>>node[i].y;
    sort(node+1,node+n+1);
    memset(dp,INF,sizeof(dp));
    for(int j=1;j<n-1;j++)
        dp[n-1][j]=dp[j][n-1]=dist(node[n-1],node[n])+dist(node[j],node[n]);
    for(int i=n-2;i>=1;i--)
        for(int j=i-1;j>=1;j--)
            dp[i][j]=min(dp[i+1][j]+dist(node[i],node[i+1]),dp[i][i+1]+dis(node[j],node[i+1]));
    cout<<dis(node[1], node[2])+dp[2][1]<<endl;
}
发布了18 篇原创文章 · 获赞 15 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/Juicewyh/article/details/84140444
今日推荐