问题描述:


双调欧几里得旅行商问题是一个经典动态规划问题。《算法导论(第二版)》思考题15-1和北京大学OJ2677都出现了这个题目。

旅行商问题描述:平面上n个点,确定一条连接各点的最短闭合旅程。这个解的一般形式为NP的(在多项式时间内可以求出)

J.L. Bentley 建议通过只考虑双调旅程(bitonictour)来简化问题,这种旅程即为从最左点开始,严格地从左到右直至最右点,然后严格地从右到左直至出发点。下图(b)显示了同样的7个点的最短双调路线。在这种情况下,多项式的算法是可能的。事实上,存在确定的最优双调路线的O(n*n)时间的算法。



HDU-2224:

题目大意:

                    给你n个点,严格规定从从起始点1走到最右端n在从n返回1,并且所有点都只经过一次,求最短路径;

题目思路:

                 看别人的博客看了好久好久,然后自己一步一步去模拟,最后也算是理解了怎么来的,下面我就说下我对该算法的理解

首先考虑这个问题的子问题,我们定义dis[i][j]为从i到j的直线距离,定义dp[i][j]  (考虑i<=j)为从i出发走到起始点(递减)再返回

走到j(递增)的最短路程,所以我们知道dp[i][j]为从起始点1走到j的最短路程,所以dp[n][n]就是我们要求的答案!   然后我们来分析下状态转移

1:  i<j-1  :

                我们来考虑下dp[i][j]可以由什么得来,我们看下j-1这个点,因为i<j-1,所以我们可以知道最后一定是由j-1走到j的,所以我们

可以得到dp[i][j]=dp[i][j-1]+dis[j-1][j]

2:  i==j-1 :

              我们再来考虑下j-1这个点,因为i=j-1所以就相当于i点,该点一定是从i点走到1- i-1中的一点,那么我们要找的是最短路程的哪一点,

所以我们可以遍历 {dp[k][i]+dis[k][j],1=<k<i}找一个最小的值,所以此时k点就是我们要找的点,所以 dp[i][j] = dp[k][i] +dis[k][j] 

 这里我们可以看成由j-i,先j-k再k-i,然后找一个最小路程


然后有了上面的分析就可以写代码了~~    可以当做模板哈~~~


AC代码:


#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cmath>

#define db double

using namespace std;

const int maxn = 205;
const db inf = 1e9;

class TSP{
public:
    int n;
    db x[maxn],y[maxn],dis[maxn][maxn],dp[maxn][maxn];

    db cal(db x1,db x2,db y1,db y2){return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));}

    void sove(){
        dp[1][2]=dis[1][2];
        for(int j=3;j<=n;j++){
            // i<j-1
            for(int i=1;i<=j-2;i++)dp[i][j]=dp[i][j-1]+dis[j-1][j];
            //i=j-1
            db Min=inf;
            for(int k=1;k<=j-2;k++)Min=min(Min,dp[k][j-1]+dis[k][j]);
            dp[j-1][j]=Min;
        }
        dp[n][n]=dp[n-1][n]+dis[n-1][n];
        printf("%.2lf\n",dp[n][n]);
    }

    int init(){
       if(scanf("%d",&n)==-1)return -1;
       for(int i=1;i<=n;i++)scanf("%lf%lf",&x[i],&y[i]);
       for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)dis[i][j]=cal(x[i],x[j],y[i],y[j]);
       sove();
    }

}tp;

int main()
{
    while(~tp.init());
    return 0;
}