欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

洛谷【P1523】旅行商的背包(算法导论 15-1) 题解

程序员文章站 2022-06-08 15:53:58
...

P1523 旅行商简化版

题目背景

欧几里德旅行商\((Euclidean Traveling Salesman)\)问题也就是货郎担问题一直是困扰全世界数学家、计算机学家的著名问题。现有的算法都没有办法在确定型机器上在多项式时间内求出最优解,但是有办法在多项式时间内求出一个较优解。

为了简化问题,而且保证能在多项式时间内求出最优解,\(J.L.Bentley\)提出了一种叫做\(bitonic\) \(tour\)的哈密尔顿环游。它的要求是任意两点\((a,b)\)之间的相互到达的代价\(dist(a,b)=dist(b,a)\)且任意两点之间可以相互到达,并且环游的路线只能是从最西端单向到最东端,再单项返回最西端,并且是一个哈密尔顿回路。

题目描述

著名的\(NPC\)难题的简化版本

现在笛卡尔平面上有\(n(n<=1000)\)个点,每个点的坐标为\((x,y)\)(\(-2^{31}<x,y<2^{31}\),且为整数),任意两点之间相互到达的代价为这两点的欧几里德距离,现要你编程求出最短\(bitonic\) \(tour\)

输入输出格式

输入格式:

第一行一个整数\(n\)

接下来\(n\)行,每行两个整数\(x,y\),表示某个点的坐标。

输入中保证没有重复的两点,

保证最西端和最东端都只有一个点。

输出格式:

一行,即最短回路的长度,保留\(2\)位小数。

输入输出样例

输入样例#1:

7
0 6
1 0
2 3
5 4
6 1
7 5
8 2

输出样例#1:

25.58

说明

来源 \(Source\)

《算法导论(第二版)》 \(15-1\)


评成绿题稍微有一点低,这个题目的思维要求还是比较高的。

这里利用到一个重要的思想:对于一个有确切起点和终点的回路,其路径情况,可以认为是同一个位置出发的两个点同时向前推进,类似的题目可以参考NOIP2008—传纸条

所以不要被这个题目的题面欺骗。实际上题目问的,就是从一个起点出发的,具有相同终点的两条路径的最小总长度!

做法可以认为是一类套路,如下:

\(F[i][j]\)为从最左侧的起点出发,一个点走到\(i\),一个点走到\(j(i>j)\),且所有\(i\)以内的点全部被走过的最短路径。

  • \(i=j+1\) 时:\(F[i][j]\)可以由\(F[j][k],k∈[1,j)\)转移得到,原因如下图
  • \(i>j+1\)时,走在前面的人上一步一定是\(i-1\),所以本情况一定由\(F[i-1][j]\)转移而来。

洛谷【P1523】旅行商的背包(算法导论 15-1) 题解

总复杂度\(O(n^2)\),思维难度还是有的。

\(Code:\)

//P1523 100pts 
#include<cmath> 
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define INF 1e50 
#define MAXN 1010
#define dbl double
using namespace std;
struct node{
    dbl x;
    dbl y;
    bool operator<(const node &rhs)const{
        return x<rhs.x;
    }//按照纵坐标从左往右排序 
}arr[MAXN];
int n;
dbl f[MAXN][MAXN];
inline dbl dis(int x,int y){
    return hypot(fabs(arr[x].x-arr[y].x),fabs(arr[x].y-arr[y].y));
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i){
        scanf("%lf%lf",&arr[i].x,&arr[i].y);
    } 
    sort(arr+1,arr+1+n);//排序
//  for(int i=1;i<=n;++i){
//      printf("%lf %lf\n",arr[i].x,arr[i].y);
//  }
    for(int i=0;i<=1000;++i){
        for(int j=0;j<=1000;++j){
            f[i][j]=INF;
        }
    }
    f[2][1]=dis(1,2);
//  printf("f[2][1]=%lf\n",f[2][1]);
    for(int i=3;i<=n;++i){
        for(int j=i-1;j>=1;--j){
            if(i==j+1){
                //刚好是向后一个的节点
                for(int k=j-1;k>=1;--k){
                    f[i][j]=min(f[i][j],f[j][k]+dis(k,i));
                } 
            }else{
                f[i][j]=min(f[i][j],f[i-1][j]+dis(i-1,i));
            }
        }
    }
    double ans=INF;
    for(int i=1;i<=n;++i){
        ans=min(ans,f[n][i]+dis(i,n));
    }
//      printf("\n");
    
    printf("%.2lf\n",ans);
}