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

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

欧几里德旅行商(Euclidean Traveling Salesman)问题也就是货郎担问题一直是困扰全世界数学家、计算机学家的著名问题。现有的算法都没有办法在确定型机器上在多项式时间内求出最优解,但是有办法在多项式时间内求出一个较优解。
为了简化问题,而且保证能在多项式时间内求出最优解,J.L.Bentley提出了一种叫做bitonic tour的哈密尔顿环游。它的要求是任意两点(a,b)(a,b)(a,b)之间的相互到达的代价dist(a,b)=dist(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)(−231<x,y<231-2^{31}<x,y<2^{31}231<x,y<231,且为整数),任意两点之间相互到达的代价为这两点的欧几里德距离,现要你编程求出最短bitonic tour 。
//还是Tip : 笛卡尔平面 : 平面直角坐标系

输入 Input

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

输出 Output

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

样例输入 Sample Input

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

样例输出 Sample Output

25.58

限制 Limits

数据范围见题目
Time Limit : 1s1s1s & Memory Limit : 128MB128MB128MB

来源 Source

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

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

#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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值