1. 程式人生 > 其它 >UVA 1347 Tour(DP) 紫書訓練

UVA 1347 Tour(DP) 紫書訓練

技術標籤:紫書第9章c++

題目連結:UVA 1347
做題感受:看到題自己想沒啥思路,也想不出將一人來回改成兩人同時走到終點,自己本來的想法是構造一個三維陣列dp[i][j][0]和dp[i][j][1]分別表示過去和回來的狀態,但是寫到後面發現狀態轉移方程寫不出來,不知道該怎麼轉移,感覺主要還是狀態轉移方程太難寫,結合lrj的分析和別人的部落格分析最後才勉強寫出來了。(其實我真沒看出來這跟DAG有特別大關係,我就覺得勉勉強強搭上邊)

(感覺思路分析太長的還是直接看概括吧,個人感覺自己思路分析寫的不太好)
思路:
紫書上寫的挺明白了,其實就是把一個人來回分成兩個人同時到達終點,但是要怎麼定義狀態呢?我們定義dp(i,j)為第一個人在i點,第二個人在j點時,離終點還有多遠,先不用管他怎麼表示多遠,dp(i,j)和dp(j,i)實際上是一樣的,無非就是表示成第二個人在i點,第一個人在j點,其實答案沒有區別,因此我們預設i>j,

這樣一來,我們可以將dp(i,j)定義為前面i個點都走過了,還需要走多遠才能到達終點,這樣定義之後,不管是哪個人,下一步只能走到i+1,i+2,i+3…以此類推,i+1沒問題,就走一步就好了,如果是走到i+2或3就會發現i+1沒走過(因為下一步i=i+2只能走到i+2以後的點,另一個人下一步也不能到達i+1這個點),那該怎麼辦,那我們就定義下一步只能走到i+1的位置,這樣定義就可以保證每個點都會走過,其實之前你直接走到i+2只有一步,我們現在就是把i+2分成了2步,每次都是i+1保證所有點都能到達(假如a沒走過的點,b會幫忙補上)(其實也就是在狀態轉移的時候要麼是dp(i+1,i)要麼是dp(i+1,j),看誰補上來)
然後就得出了狀態轉移方程
dp(i,j)=min(dp(i+1,j)+d(i,i+1),dp(i+1,i)+d(i+1,i)+d(j,i+1)).
最後就是要設定好邊界條件dp[n-1][x]表示即將到達終點,所有點都走過了,只要兩人各走一步就走到終點.

簡要概括思路:一人分成兩人,將狀態定義為dp(i,j),每次只能讓兩個人中的一個能走到i+1的位置(把很長的一步分成一個個小步,每次只到下一個點),設定好邊界dp[n-1][x],最後得出狀態轉移方程dp(i,j)=min(dp(i+1,j)+d(i,i+1),dp(i+1,i)+d(i+1,i)+d(j,i+1)).
d表示距離,dp用來遞迴.

下面是我的程式碼:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
int n;
double dp[1005][1005];//表示a在第i個位置,b在第j個位置的時候距離終點還有多遠
struct dian {
  double x, y;
 dian(double x = 0,double y = 0):x(x),y(y){}//這條沒啥用,可以無視
}a[205];
double d(int i, int j)//計算兩點間距離
{
    return sqrt((a[i].x - a[j].x) * (a[i].x - a[j].x) + (a[i].y - a[j].y) * (a[i].y - a[j].y));
}
double jie(int i, int j)//遞迴dp
{
    if (dp[i][j] > 0)return dp[i][j];
    return dp[i][j] = min(jie(i + 1, j) + d(i,i+1), jie(i + 1, i) + d(j,i+1));
}
int main()
{
    int i, j;
    while (scanf("%d",&n)!=EOF)
    {
        memset(dp, 0, sizeof(dp));
        for (i = 1; i <= n; i++)
        {
            cin >>a[i].x >> a[i].y;
        }
        for (i = 1; i < n-1; i++)
        {
            dp[n - 1][i] = d(n-1,n)+d(i,n);//先將邊界的值定義好
        }
        double ans = jie(1, 1);//引用遞迴函式
        printf("%.2f\n",ans);
    }
    return 0;
}