TSP问题+例题

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=1217

    这道题是tsp板子题,不会做硬钢了两天,看了题解学了tsp,现在有点似懂非懂,简单记录一下.

    欧几里得旅行商问题是对平面上给定的n个点确定一条连接各点的最短闭合旅程的问题,下图a给出了7个点问题的解。这个问题的一般形式是NP完全的,故其解需要多于多项式的时间。

                 

                    


                     (a)                                                          (b)

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

   描述一个确定最优双调路线的O(n^2)时间的算法,可以假设任何两点的x坐标都不相同。

 

 

将各个节点从左到右排序,编号为1,2,3,.....,n。对于任意的i和j(其中1<=i,j<=n)。

现在有两条路径A、B都是从点1出发,A走的路径为1~ i ,B走的路径为1~ j ,

即A、B有公共的起点,但途中没有交叉点(即终点之前不存在 i=j ),终点可能重合(i=j),也可能不重合(i≠j),这取决与我们要求的问题。

令s=max(i,j),则从1到s所有的点一定在路径A或者路径B上,不会有遗漏的点.

对于特定的 i 和 j ,路径A、B存在多种可能的走法,其中比有一种2条路径的和最小的走法,我们把这2条路径的和记为b[i,j];当i=j时,b[i,j]表示了从1到 i 的双调TSP的解;当i=j=n时 b[i,j] 就表示了整个问题的最终解法。

我们可以采用DP(动态规划)求解



上图表示对b[i,j]的递推情况,开始时,显然b[i,j]=0,而i=0或j=0也可以直接确定b[i,j]的值;

基于对称的考虑,表的左下半部和右上半部的数值将完全相同,所以在生成表的时候可以不用考虑下半部分

如何求递推?分一下几种情况

① i > j (即图的右上部分)



已知b[i,j] ,求b[i+1,j],只要将A直接延长到 i+1就行。

即 b[i+1,j] = b[i,j] + distance(i,i+1)

 

② i = j (对角线部分)

假设已知b[i,i],求b[i+1,i],可以想象,此时AB两条路径在终点 i 相交,因为现在我们要求A的终点为i+1,所以不得不把相交的AB在i点拆开。

 



事情没那么简单,拆开后,我们需要在路径A找任意点u(0<=u<=i),使点u连接点i+1.

由于b[ u , j ] 是A路径到u,B路径到j,经过max(u,j)内所有点,所以b[ u , j ]+distance( i , u ) = b[ i , j ]

我们需要找到 min{ b[u,j] + distance(u, i+1)},即遍历 b[1..i, j],刚好这是图的左下部分,因为这个部分是和右上部分对称的,所以可以直接利用右上部分的数据。

b[i+1,j] = min{ B[u,i] + w(u,i+1) }

③ 确定b[n,n],即确定最终的解。

前面 ①②部分已经可以把所有的点都走一次了,现在我们最后需要做的是,把AB路径的头连接起来。

那怎么连接才能让AB路径加起来最短呢?

前面我们说过: s=max(i,j),则从1到s所有的点一定在路径A或者路径B上,不会有遗漏的点

通过①②我们已经求出 1~s 点的所有数据。

也就是我们可以通过 min{ b[n,k] + distance(n,k)} 其中 1<=k<n 求出b[n,n];

b[n,n] =  min{ b[n,k] + distance(n,k)}

 

总结一下:

 b[i,j] = b[i-1,j] + distance(i-1,i) (j+1 < i ,蓝色部分)

 b[i,j] = min{ B[u,i] + w(u,i+1) } (j+1 = i ,红色部分)

b[n,n] =  min{ b[n,k] + distance(n,k)}  0<=k<n

转自大佬博客:https://blog.csdn.net/qq742762377/article/details/85050040

 附加:想到一个问题,有人问我,我们既然可以从(0,0)递推到(1,0),递推到(2,0).......到(n,0) 为什么直接从(2,0)推到(2,1)呢?

   解释一下,根据我们的定义在(2,0)的时候,一号点是已经走过了,所以如果我们要从(2,0)推(2,1)的话就会发生这样的事情

这个代表(2,0),推到到(2,1)的时候就变成了这样

这样肯定不对啊,因为两条路在1号点就相交了所以(2,1)只能根据(1,0)推出来

题目ac代码:

#include<bits/stdc++.h>
using namespace std;
#define INT_MAX 0x73f3f3f
typedef struct W_W{
    int a;
    int b;
}miao;
miao x[1010];
double weight[1010][1010];
double dp[101][1010];
double jl(int a,int b){
    return sqrt((x[a].a-x[b].a)*(x[a].a-x[b].a)*1.0+(x[a].b-x[b].b)*(x[a].b-x[b].b)*1.0);
}
bool cmp(miao a,miao b){
    return a.a<b.a;
}
int main()
{
    int n;
    while(~scanf("%d",&n)){
        for(int i=0;i<n;i++){
        scanf("%d %d",&x[i].a,&x[i].b);
        }
        sort(x,x+n,cmp);
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                dp[i][j]=-1.0;
                weight[i][j]=0.0;
            }
        }
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(i==j){
                    weight[i][j]=0;
                }
                else{
                    weight[i][j]=jl(i,j);
                }
            }
        }
        dp[0][0]=0;
        dp[1][0]=weight[1][0];
        for(int i=2;i<n;i++){
            for(int j=0;j<i;j++){
                if(i!=j+1){
                    dp[i][j]=dp[i-1][j]+weight[i][i-1];
                }
                else{
                    double minn=INT_MAX;
                    for(int k=0;k<j;k++){
                        minn=min(minn,dp[j][k]+weight[i][k]);
                    }
                    dp[i][j]=minn;
                }
            }
        }
        double ans=INT_MAX;
        //printf("%d
",dp[1][1]);
    //    for(int i=0;i<n;i++){
    //        for(int j=0;j<n;j++){
    //            printf("%.2lf ",dp[i][j]);
    //        }
    //        printf("
");
    //    }
        for(int i=0;i<n-1;i++){
            ans=min(dp[n-1][i]+weight[n-1][i],ans);
        }
        printf("%.2f
",ans);

    }
    return 0;
}
原文地址:https://www.cnblogs.com/fzw1523/p/11184440.html