UVa1347/poj2677 - C - Tour(DP)

UVa1347/poj2677 - C - Tour

题意:有n个点,一飞行员从1号点出发,严格从左走到右在返回1号点(同样严格从右走到左),求经过除1号点和n号点外每个点恰好一次的整个行程的最小值。

 

分析:与货郎担问题很相似,只是这里是先到n号点在返回走完未走的点,很多博主称之为双调欧几里得旅行商问题,紫书上面也说这是个经典问题,这题确实值得一做。

刚开始顺理成章地状态压缩dp了一次,因为必须要知道哪些点已经过才行,也顺利的过了样例,交上去RE…..题目的点数没给定范围,所以刚开始根本不知道,后面知道点很多之后,就再也不知道怎么进行DP了….无奈只能看紫书上的分析,解法真是奇妙!!!

首先是一个转换问题,既然人从n号点返回是走完之前未走的点,那么等价转换为两个人同时从1号点出发,总共不重复地走完中间的n-2个点,最后到达n号点,求整个过程的最短距离,那么定义dp[i][j]为第一个人走到i号点,第2个人走到j号点时整个过程的最短行程,因为不知道第一个人走到i号点,第2个人走到j号点时之前的点是否都走过,那么便重新定义dp[i][j](假定这里i>j)为两个人走完1到i号点中所有点的最短行程。dp[i][j]等于dp[j][i],因为是对称的,将两个人交换就可以了,那么决策呢?我们可能会想每个人都可以走i+1后的这些点,但是这样是不符合状态的定义的,因为没有保证每个点都走过,在这样定义的状态中,每个状态转移时不应该存在这样的决策的,要保证每个点都走过的话,那么对于一个新的点i+1,要么第一个人走,要么第二个人走,所以dp[i][j]可以转移至dp[i+1][j],dp[i+1][i],这样的决策会不会存在问题呢?借用紫书上面的话:如果第一个人直接走到了i+2这个点,那么就由第二个人走到了i+1这个点,而存在这样的转移,所以整个规划的过程在根据定义的状态和合理的决策递推的同时也保证了解的可行性和正确性,所以说不同的状态定义会有不同的规划过程,决策自然不一定是相同的。

 

感觉这个题目需要好好研究下,相信如果深刻理解了这个题,对于DP的理解会加深一些。

在这个题目中,采用更新方式更为简单明了,状态转移方程也可以,但是对某一个状态来说,它可以有很多种决策转移至它,所以说复杂一些。(紫书上所说的刷表法和填表法)


#include 
#include 
#include 
#include 
#define INF 0x3f3f3f3f
#define LL long long
using namespace std;

struct po{int x, y;}p[1010];
double dp[1010][1010], dis[1010][1010], ans;

int main()
{
    int n;
    while (~scanf("%d", &n)){
        for (int i = 1; i <= n; i++) scanf("%d %d", &p[i].x, &p[i].y);
        for (int i = 1; i < n; i++){
            dis[i][i] = 0;
            for (int j = i+1; j <= n; j++) dis[j][i] = dis[i][j] = sqrt((p[i].x-p[j].x)*(p[i].x-p[j].x)+(p[i].y-p[j].y)*(p[i].y-p[j].y));
        }
        for (int i = 1; i <= n; i++) fill(dp[i]+1, dp[i]+1+n, INF);
        dp[1][1]  = 0;
        for (int i = 1; i <= n; i++){
            for (int j = 1; j <= n; j++){
                dp[j][i+1] = dp[i+1][j] = min(dp[i+1][j], dp[i][j]+dis[i][i+1]);
                dp[i][i+1] = dp[i+1][i] = min(dp[i+1][i], dp[i][j]+dis[j][i+1]);
            }
        }
        printf("%.2f\n", dp[n][n]);
    }
    return 0;
}


你可能感兴趣的:(动态规划)