uva 1347 - Tour(双调欧几里得旅行商问题)

题目链接

Description

给定平面上n个点的坐标(按照x递增的顺序给出。各点的x坐标不同,且均为正整数),你的任务是设计一条路线,从最左边的点出发,走到最右边的点然后返回,要求除了最左边和最右边的点之外每个点恰好经过一次,且路径的总长度最短。两点之间的距离为他们的欧几里得距离。

下图(b)对应双调欧几里得旅行商问题的最优解。

                                      

解析

我们将问题转化

将所有的点按照x坐标的大小从小到大一次编号,下面我们的所说的i,j都是坐标的编号。

假设现在有两个人A,B,且两个人分别从最左边的点出发沿着不同的路线到达最右边的点。

也就是说A走过的点B不会再走,B走过的点A不会再走。

我们定义状态方程dp[i][j]

表示A从最左边走到i位置,B从最左边走到j位置的时候,到达最右边还需要走的最小总距离。并且当到达状态dp[i][j]的时候,A,B两个人已经走过1~max(i,j)的所有点。

我们根据状态方程的定义不难得出,dp[i][j]具有对称性质,也就是dp[i][j] = dp[j][i]。

因此下面我们的讨论默认i>=j;

下面我们考虑状态转移方程

对于状态dp[i][j],我们需要找到他的前一个状态,也就是距离最右边的点更近的状态。

因为i>=j的所以我们可以知道dp[i + 1][j]是dp[i][j]的前一个状态,dp[i][j] = dp[i + 1][j] + distance[i][i + 1]。

那么还有没有其他的前一个状态呢?

首先根据定义我们可以分析出来如果是A要走那么A必然要走到i+1,因为到达dp[i][j]的同时要求走过1~max(i,j)的所有点。

因此如果是A走到i+2等位置不能够保证这个条件成立。

那么必然是j走到i+1位置,根据对称性质我们知道dp[i][i+1] = dp[i + 1][i]也是dp[i][j]的前一个状态,且dp[i][j] = dp[i + 1][i] + distance[j][i + 1]。

再进一步解释

首先dp[i + 1][j]是dp[i][j]的前一个状态,dp[i][j] = dp[i + 1][j] + distance[i][i + 1]应该比较好理解,也就是A向前走一步就行了。

但是dp[i][i+1] = dp[i + 1][i]为什么也是dp[i][j]的下一个状态?

我们再说明一下dp[i][j]不仅仅是表明最短总距离,同时也表示了1~max(i,j)当中的点全部都走过了一遍。

因此我们可以断定AB两个人下一步都不能是i+2这个位置的点 ,只能是i+1这个位置,并且是AB当中的一个人到达i+1位置,因为如果是两人同时到达违反了题目给出的条件“恰好一次”。

边界问题(初始化)

对于状态dp[n - 1][j]首先我们知道1~n-1之间的所有点已经走过了,我们只要一步到n就行了,因此dp[n - 1][j] = distance[n - 1][n] + distance[j][n];

AC代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 5000 + 5;
double dis[maxn][maxn], dp[maxn][maxn];
pair<int, int> point[maxn];
int n;

double get_dis(const int &x, const int &y)
{
    return sqrt((point[x].first - point[y].first) * (point[x].first - point[y].first) + (point[x].second - point[y].second) * (point[x].second - point[y].second));
}

double dfs(const int &x, const int &y)
{
    if(dp[x][y] > 0)
        return dp[x][y];
    if(x == n - 1)
        return dp[x][y] = dis[n - 1][n] + dis[y][n];
    return dp[x][y] = min(dfs(x + 1, y) + dis[x][x + 1], dfs(x + 1, x) + dis[y][x + 1]);
}

int main()
{
    while(scanf("%d", &n) != EOF)
    {
        for(int i = 1; i <= n; i++)
            scanf("%d%d", &point[i].first, &point[i].second);

        for(int i = 1; i <= n; i++)
            for(int j = i + 1; j <= n; j++)
                dis[i][j] = dis[j][i] = get_dis(i, j);
        memset(dp, 0, sizeof dp);

        printf("%.2f\n", dfs(1, 1));
    }
    return 0;
}
发布了76 篇原创文章 · 获赞 18 · 访问量 2751

猜你喜欢

转载自blog.csdn.net/qq_43446165/article/details/103889783
今日推荐