hdu2224——双调欧几里德旅行商问题

本题为《算法导论》动态规划一章中思考题的第一题。

原题目中假设任意两点的x坐标均不同,hdu2224中并没有说明,但后台数据应该是没有x坐标重复点的。

这道题目,网上写解法的文章很多,但是很多都写得很乱,有些甚至是错误的。在这里我想好好澄清一下O(n^2)的解法及其原理,希望能起到抛砖引玉的作用。

 

原题大意:平面n个不同的点(假设任意两点x坐标不同),旅程从最左点开始,严格地从左到右直至最右点,然后严格地从右到左至最左点。求最短双调路线。

解法:

(1)对n个点按x坐标排序,我们用dp[i][j]表示其中一条路径走到i点,另一条路径走到j点(且i和j并不相连);则dp[i][j]==dp[j][i](想一想为什么),所以我们仅需要计算一个,我们只计算dp[i][j] (i<=j的情况)。另外,由几何学可以知道,如果存在交错路径,那该双调路径必然不是最优路径(我也不会从数学理论上严格证明T_T,但是画一画图很直观)。因此计算dp[i][i]没有意义(除了最右边一个点)。

(2)分两种情况:1. i<j-1,这种情况下,dp[i][j]只能由dp[i][j-1]进行转移,因为路径要求严格从左至右(到达最右点之后严格从右至左),所以能到达j点的只能是点j-1,否则的话j-1点没有被经过,与题设严格从左至右矛盾。此时dp[i][j]=dp[i][j-1]+dis[j-1][j]; 2. i==j-1,这种情况下,j点必由某一点k直接到达,则路径可分为一条路径从1到i;另一条路径从1到k,然后从k到j。计算方程时应该这么算:dp[i][j]=min(dp[i][j], dp[i][k]+dis[k][j]),有童鞋就疑问了,dis[i][k]此时肯定没有被计算啊,因为这种情况下必定有k<i,没错,dis[i][k]没有被计算,但是dis[k][i]计算过了,而且dis[i][k]和dis[k][i]是等价的,所以方程可改写为:dp[i][j]=min(dp[i][j], dp[k][i]+dis[k][j])。还有童鞋疑问,那么从点k+1到点j-2之间的点会不会有遗漏呢?肯定不会!因为这种情况下按题设和转移方程,点k+1,k+2,....,j-2肯定在从1到j-1(即从1到i)这条路径上。总之,一旦你觉得方程有问题可能会遗漏点,就想一下题设:严格从左至右,然后仔细理解一下第一种情况下的方程,问题就迎刃而解了。初始dp[1][2]=dis[1][2],最终结果为dp[n-1][n]+dis[n-1][n]   (n==2的情况单独处理)。

(3)复杂度

排序O(n*logn);

对于每个j,若i<j-1,则求dp[i][j]的转移费用为O(1),若i==j-1,则转移费用O(n),所以对于每一个j转移费用为O(n)+O(n)=O(n),共有n个j,所以总费用为O(n^2)。

所以复杂度为O(n*logn)+O(n^2)=O(n^2)。

我觉得我已经解释的比较详细了,如果还有哪里不明白或者我写的不清楚,欢迎提出。

下面是hdu2224的代码,该代码也能过poj2677。

#include <iostream>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 205;
const double inf = 1e20;

struct node {
    int x, y;
}p[N];
int n;
double dp[N][N], dis[N][N];

bool cmp(const node &a, const node &b) {
    if(a.x == b.x)  return  a.y < b.y;
    else  return  a.x < b.x;
}

double calDis(node &a, node &b) {
    double tx = a.x - b.x, ty = a.y - b.y;
    return  sqrt(tx*tx+ty*ty);
}

int main()
{
    //freopen("data.in", "r", stdin);
    while(scanf("%d", &n) != EOF) {
        for(int i = 1; i <= n; i++)  scanf("%d%d", &p[i].x, &p[i].y);
        sort(p+1, p+n+1, cmp);
        for(int i = 1; i <= n; i++)
            for(int j = i+1; j <= n; j++) {
                dis[i][j] = dis[j][i] = calDis(p[i], p[j]);
            }
        dp[1][2] = dis[1][2];
        for(int j = 3; j <= n; j++) {
            for(int i = 1; i < j-1; i++)  dp[i][j] = dp[i][j-1]+dis[j-1][j];
            dp[j-1][j] = inf;
            for(int i = 1; i < j-1; i++)
                dp[j-1][j] = min(dp[j-1][j], dp[i][j-1]+dis[i][j]);
        }
        double ans;
        if(n == 2)  ans = dis[n-1][n];
        else  ans = dp[n-1][n] + dis[n-1][n];
        printf("%.2f\n", ans);
    }
    return 0;
}
posted @ 2012-09-11 10:27  fCarver7  阅读(408)  评论(1编辑  收藏  举报