题目链接: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;
}