前言
实验周的题目,使用动态规划法解决,感觉这个题目挺不错的。因为动态规划法用的不是很多,理解的也不是太透彻,所以把这道题目拿出来。
问题描述
章鱼博士绑架了蜘蛛侠的女友乔丹把她关在西塔上,蜘蛛侠必须尽快到西塔去救她通过使用他的武器——蜘蛛网。蜘蛛侠的公寓到塔之间有一条笔直的路,路旁有许多高大的建筑物并且高度都不低于他的公寓。蜘蛛侠可以使用他的蜘蛛网到达大楼的另一边,图1显示了蜘蛛侠从他的公寓到B,从B到C,从C到塔。所有的建筑物(包括塔)被视为直线,并且在他划摆期间不能碰到地面这意味着蜘蛛网的长度不能大于建筑物的高度,在蜘蛛侠划摆期间不能倒退。
输入要求:
输入的第一行包含测试用例的数量K(1<=K<=20),每个案例都以一个单独的整数N开始,K表示建筑物的数量,接下来的N行中每行包括两个整数Xi, Yi, (0 <= Xi, Yi <= 1000000)表示建筑物的位置和高度。第一个建筑是公寓而最后一个建筑是塔,以Xi的值递增输入而且不重复输入相同的Xi的值。
输出要求:
对于每一个测试案例,输出一行划摆的最少次数,如果不能到达塔则输出-1。
问题分析
对于公寓到塔的距离之间的任意一个位置P,可以有多种方式到达该点,题目的要求是在到达终点的多种方式中选择一个划摆次数最少的方式,这种在运行过程中不断用得到的数据去更新目标数据的方法和动态规划法有异曲同工之妙,所以本问题选择使用动态规划法来求解。
首先考虑的问题是对于任意一个建筑物Ti(Xi, Yi),如果能够从该塔进行划摆,那么此次划摆的起点(Xi-1,Yi-1)与建筑物Ti(Xi, Yi)应该要满足一定的关系式。
从题目的分析中可以得到,蜘蛛侠在划摆的过程中不能够碰到地面,这个就意味着蜘蛛网的长度不能够大于建筑物的高度,否则蜘蛛侠在划摆作圆周运动时因为半径过长而碰到地面则不符合题意。又因为每一次的划摆过程,不论其位置X为和值,其高度Y必等于公寓的高度Y0,这可以利用物理知识或者直接从图1中可以看出来。这样我们就可以求出划摆的起点(Xi-1,Yi-1)与建筑物Ti(Xi, Yi)之间满足的关系式,如下:
这里利用勾股定理来计算建筑物Ti的有效起点距离L,这里的有效起点距离L如图所示:
L的物理意义是凡是一次划摆的起点在L之内,那么都可以从建筑物Ti完成一个划摆。这也成为判断蜘蛛侠是否可以滑到终点塔的依据。如果有任意一次的划摆不满足这样的条件,那么就可以输出-1认为无法到达终点而不用再继续运行程序。
当得到任意一个建筑物Ti的有效起点距离之后,那么在该距离之内的所有点都将对建筑物Ti之后的点产生影响,即根据距离之内的点的值去修改建筑物之后的距离的值。这里我们使用swing[Xi]来表示在Xi之前已进行的划摆次数,同时我们取建筑物Ti的有效起点距离L之内的一个点J(Xj,Y0),从点J开始进行划摆,当划过建筑物Ti之后所达到的点D(Xd,Yd)满足如下的关系式:
那么swing[Xd]的值按理就应该是swing[Xj]+1,因为从点J进行了一次划摆到达了点D。但是按照前面所说,到达点D的方式可能存在很多种,我们需要的只是其中划摆次数最少的一次,所以在修改swing[Xd]的值之前我们首先应该做的是判断swing[Xd]当前的值与swing[Xj]+1的大小,如果swing[Xj]+1的值小于swing[Xd]的值,那么我们就应该修改它,表示从点J进行划摆到达点D所需要的总共划摆次数是当前最少的。
从开始我就说明了此次算法采用的是动态规划法的框架,根据前面的运行结果去修改当前的值的做法在修改swing[Xd]的值的时候得到了很好的体现,这也证明了我采用算法是基本正确的。
既然是动态规划法,那么就一定存在一个状态转移方程,当前值的修改是在满足状态转移方程的条件下进行修改,从前面的分析就可以很容易写出关于swing[Xd]的状态转移方程了。
得到了状态转移方程之后,我们对每一个建筑物都进行求解得到有效起点距离,然后对距离之内的每一个点按照状态转移方程来修改swing[Xd]的值,当所有的建筑物都考虑之后,那么我们直接看swing[Xn]的值即可。如果swing[Xn]的值为-1则表示不能到达终点,反之得到的一个整数值就是从公寓到塔所划摆的最少次数。
动态规划法的使用条件是当前的值是前面所有结果共同作用的,动态的去修改的值,因为此题的swing[Xn]的求解满足该条件,所以动态规划法的分析方法是求解此题的关键所在。
源码参考
#include<iostream>
using namespace std;
#include<cmath>
#define MAX 5000
#define MAX_SWING 2000001
int x[MAX],y[MAX],T,N,Distance[MAX], swing[MAX_SWING];
int getCount();
int min(int a,int b)
{
return a<b?a:b;
}
int main()
{
cin>>T;
while(T--)
{
cin>>N;
for(int i=0;i<N;i++){
cin>>x[i]>>y[i];
Distance[i] = y[i]*y[i]-(y[i]-y[0])*(y[i]-y[0]);
}
for(int j=0;j<MAX_SWING;j++)
swing[j] = -1;
cout<<getCount()<<endl;
}
return 0;
}
int getCount()
{
swing[x[0]] = 0;
for(int i=1;i<N;i++){
for(int j = x[i];j>=x[0];j--){
if(swing[j]==-1)
continue;
if(((x[i]-j)*(x[i]-j))>Distance[i])
break;
if(swing[2*x[i]-j] == -1)
swing[2*x[i]-j] = swing[j] + 1;
else
swing[2*x[i]-j] = min(swing[2*x[i]-j],swing[j]+1);
if(2*x[i]-j >= x[N-1] && (swing[x[N-1]] == -1 || swing[x[N-1]] > swing[2*x[i]-j]))
swing[x[N-1]] = swing[2*x[i]-j];
}
}
return swing[x[N-1]];
}

被折叠的 条评论
为什么被折叠?



