[DP] [Vijos P1014] 旅行商简化版 (bitonic)

//放假前一天考这么BT的DP…
背景 Background

欧几里德旅行商(Euclidean Traveling Salesman)问题也就是货郎担问题一直是困扰全世界数学家、计算机学家的著名问题。现有的算法都没有办法在确定型机器上在多项式时间内求出最优解,但是有办法在多项式时间内求出一个较优解。
为了简化问题,而且保证能在多项式时间内求出最优解,J.L.Bentley提出了一种叫做bitonic tour的哈密尔顿环游。它的要求是任意两点 ( a , b ) (a,b) (a,b)之间的相互到达的代价 d i s t ( a , b ) = d i s t ( b , a ) dist(a,b)=dist(b,a) dist(a,b)=dist(b,a)且任意两点之间可以相互到达,并且环游的路线只能是从最西端单向到最东端,再单项返回最西端,并且是一个哈密尔顿回路。
//Tip : 哈密尔顿回路 : 通过图中每个顶点一次且仅一次的回路。

题目描述 Description

著名的NPC难题的简化版本
现在笛卡尔平面上有 n ( n ≤ 1000 ) n(n≤1000) n(n1000)个点,每个点的坐标为 ( x , y ) (x,y) (x,y)( − 2 31 < x , y < 2 31 -2^{31}<x,y<2^{31} 231<x,y<231,且为整数),任意两点之间相互到达的代价为这两点的欧几里德距离,现要你编程求出最短bitonic tour 。
//还是Tip : 笛卡尔平面 : 平面直角坐标系

输入 Input

第一行一个整数 n n n
接下来 n n n行,每行两个整数 x , y x,y x,y,表示某个点的坐标。
输入中保证没有重复的两点,
保证最西端和最东端都只有一个点。
//第三个Tip : 数据同时保证任意两点的横坐标不同

输出 Output

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

样例输入 Sample Input

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

样例输出 Sample Output

25.58

限制 Limits

数据范围见题目
Time Limit : 1 s 1s 1s & Memory Limit : 128 M B 128MB 128MB

来源 Source

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

考试0分…
看到题之后是不是很想打个Floyed? n 3 n^3 n3过不去哦…
那么就想想DP
一个人只能单调从起点走到终点,再由终点走到起点,题里说两点间距离 d i s ( a , b ) = d i s ( b , a ) dis(a,b)=dis(b,a) dis(a,b)=dis(b,a),所以可以把一个人拆成两个人走这个路程
那么** d p [ i ] [ j ] dp[i][j] dp[i][j]就可以表示当第一个人走到i点,第二个人走到j点所取得的最短路程**
接下来就得让两个人走起来
但是走之前还得注意一个问题, d p [ i ] [ j ] = d p [ j ] [ i ] dp[i][j]=dp[j][i] dp[i][j]=dp[j][i]
why?
第一个人走到 i i i点,第二个人走到 j j j点和第二个人走到 i i i点,第一个人走到 j j j点的最短距离如果不等,那么求得的 d p [ i ] [ j ] dp[i][j] dp[i][j] d p [ j ] [ i ] dp[j][i] dp[j][i]一定有一个不是最短距离(…)
好了,那么用什么让他走起来
相邻的两个点之间一定没有别的点(Tip3所说,大家自行理解)
所以下一个要走的点是什么东西 + 1 +1 +1
是什么东西呢?
现在两人位置的横坐标的最大值 + 1 +1 +1(假如第一个人到 i i i位置,第二个人到 j j j位置,下一个位置就是 m a x ( i , j ) + 1 max(i,j)+1 max(i,j)+1
因为最大的横坐标之前已经处理完了,可以用 i i i位置到 j j j位置的距离…
上代码

#include <cmath>
#include <cstdio>
#include <algorithm>
#define MAXN 1010
#define INF 1e50
using namespace std;

struct point
{
    double x;
    double y;
};

double get_dis(point a,point b)
{
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

bool cmp(point a,point b)
{
    return a.x<b.x;
}

point a[MAXN];
double dp[MAXN][MAXN];
int n;

double mymin(double a,double b)
{
    return a-b<0?a:b;
}

int myminint(int a,int b)
{
    return a<b?a:b;
}

int mymax(int a,int b)
{
    return a>b?a:b;
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%lf %lf",&a[i].x,&a[i].y);
    sort(a+1,a+n+1,cmp);//按x升序排序
    for (int i=1;i<=n;i++)//初值
        for (int j=1;j<=n;j++)
            dp[i][j]=INF;
    dp[1][1]=0.0;//第一个,第二个人都在1点是初始状态,距离为0
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
        {
            int nxt=myminint(n,mymax(i,j)+1);//nxt点为下一个要考察的点
                                            //dp[i及之前][j及之前]已经处理完毕,由于对称性,考察最大位置+1即可,加min防治越界
            dp[nxt][j]=mymin(dp[nxt][j],dp[i][j]+get_dis(a[i],a[nxt]));//让第一个人走到nxt点
            dp[i][nxt]=mymin(dp[i][nxt],dp[i][j]+get_dis(a[j],a[nxt]));//让第二个人走到nxt点
        }
    printf("%.2lf",dp[n][n]);
    return 0;
}

DP瞎搞#1

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值