算法-动态规划-Help Jimmy

题目描述

"Help Jimmy" 是在下图所示的场景上完成的游戏:

算法-动态规划-Help Jimmy_第1张图片

场景中包括多个长度和高度各不相同的平台。地面是最低的平台,高度为零,长度无限。
Jimmy老鼠在时刻0从高于所有平台的某处开始下落,它的下落速度始终为1米/秒。当Jimmy落到某个平台上时,游戏者选择让它向左还是向右跑,它跑动的速度也是1米/秒。当Jimmy跑到平台的边缘时,开始继续下落。Jimmy每次下落的高度不能超过MAX米,不然就会摔死,游戏也会结束。
设计一个程序,计算Jimmy到地面时可能的最早时间。

输入

第一行是测试数据的组数t(0 <= t <= 20)。每组测试数据的第一行是四个整数N,X,Y,MAX,用空格分隔。N是平台的数目(不包括地面),X和Y是Jimmy开始下落的位置的横竖坐标,MAX是一次下落的最大高度。接下来的N行每行描述一个平台,包括三个整数,X1[i],X2[i]和H[i]。H[i]表示平台的高度,X1[i]和X2[i]表示平台左右端点的横坐标。1<= N <= 1000,-20000 <= X, X1[i], X2[i] <= 20000,0 < H[i] < Y <= 20000(i = 1..N)。所有坐标的单位都是米。
Jimmy 的大小和平台的厚度均忽略不计。如果Jimmy 恰好落在某个平台的边缘,被视为落在平台上。所有的平台均不重叠或相连。测试数据保Jimmy一定能安全到达地面。

输出

对输入的每组测试数据,输出一个整数,Jimmy到地面时可能的最早时间。

样例输入 
1
3 8 17 20
0 10 8
0 10 13
4 14 3
样例输出 
23
思路分析 

Jimmy跳到一块板上后,可以有两种选择,向左走,或向右走。走到左端和走到右端所需的时间,是很容易算的。如果我们能知道,以左端为起点到达地面的最短时间,和以右端为起点到达地面的最短时间,那么向左走还是向右走,就很容易选择了。因此,整个问题就被分解成两个子问题,即Jimmy所在位置下方第一块板左端为起点到地面的最短时间,和右端为起点到地面的最短时间。这两个子问题在形式上和原问题是完全一致的。将板子从上到下从1开始进行无重复的编号(越高的板子编号越小,高度相同的几块板子,哪块编号在前无所谓),那么,和上面两个子问题相关的变量就只有板子的编号,所以,本题目的“状态”就是板子编号,而一个“状态”对应的“值”有两部分,是两个子问题的解,即从该板子左端出发到达地面的最短时间,和从该板子右端出发到达地面的最短时间。不妨认为Jimmy开始的位置是一个编号为0,长度为0 的板子,假设LeftMinTime(k)表示从k号板子左端到地面的最短时间,RightMinTime(k)表示从k号板子右端到地面的最短时间,那么,求板子k左端点到地面的最短时间的方法如下:

if (板子k左端正下方没有别的板子)

         if(板子k的高度h(k)大于Max)      LeftMinTime(k) = ∞;

         else       LeftMinTime(k) = h(k);

else if(板子k左端正下方的板子编号是m )

        LeftMinTime(k) = h(k)-h(m) + Min( LeftMinTime(m) + Lx(k)-Lx(m), RightMinTime(m) +                    Rx(m)-  Lx(k));

上面,h(i)就代表i号板子的高度,Lx(i)就代表i号板子左端点的横坐标,Rx(i)就代表i号板子右端点的横坐标。那么h(k)-h(m)当然就是从k号板子跳到m号板子所需要的时间,Lx(k)-Lx(m)就是从m号板子的落脚点走到m号板子左端点的时间,Rx(m)-Lx(k)就是从m号板子的落脚点走到右端点所需的时间。

求RightMinTime(k)的过程类似。

不妨认为Jimmy开始的位置是一个编号为0,长度为0的板子,那么整个问题就是要求LeftMinTime(0)。输入数据中,板子并没有按高度排序,所以程序中一定要首先将板子排序。

代码实现
#include 
#include 
#include 
using namespace std;

#define INF 100000
int lmt[1005];     //走左边的最小时间
int rmt[1005];     //走右边的最小时间
int k,m,n;

struct platform{  //定义平台类型的结构体
	int x1,x2,h;
}p[1005];

int cmp(const void*a,const void*b)
{
	struct platform* m=(struct platform*)a;
	struct platform* n=(struct platform*)b;
	return n->h - m->h;
}

int min(int k,bool left)    //k表示当前这块板的编号,bool left表示是否向左走
{
	int a,b,i;
	b=p[k].h;            //初始化a,b坐标
	if(left) a=p[k].x1;     //向左走,就到左边边上
	else a=p[k].x2;         //向右走,就到右边边上
	for(i=k+1;i<=n;i++){       //找到这块板的下一块板
		if(p[i].x1<=a&&p[i].x2>=a) break;
    }
	if(i==n+1){         //这块板为最后一块板
		if(b>m) return INF;
		else return b;
	}
	if(b-p[i].h>m) return INF;    //这块板与下一块板之间的高度差过大
	int lt=b-p[i].h+a-p[i].x1;
	int rt=b-p[i].h+p[i].x2-a;
	if(lmt[i]==-1){        //表示初始化过,还可以向左
		lmt[i]=min(i,true);     //动态规划,向左进入子问题
	}
	if(rmt[i]==-1){          //表示初始化过,还可以向右
		rmt[i]=min(i,false);     //动态规划,向右进入子问题
	}
	lt+=lmt[i];
	rt+=rmt[i];
	if(lt>rt) return rt;
	else return lt;
}

int main()
{
	int t,x,y;
	int i,j;
	cin>>t;
	while(t--){
		memset(lmt,-1,sizeof(lmt));
		memset(rmt,-1,sizeof(rmt));
		cin>>n>>x>>y>>m;
		p[0].x1=x;
		p[0].x2=x;
		p[0].h=y;
		for(i=1;i<=n;i++){
			cin>>p[i].x1>>p[i].x2>>p[i].h;
		}
		qsort(p,n+1,sizeof(p[0]),cmp);
		cout<

注意:

1. 在C语言中,函数也可以包含多个return语句。当函数执行到某个return语句时,它会立即返回相应的值并结束函数的执行。这种方式可以让函数根据不同的条件返回不同的值。

2.使用"using namespace"语句可以方便地将整个命名空间中的所有内容引入到当前作用域中,这样就不需要在代码中反复使用命名空间的限定符。需要注意的是,使用"using namespace"会将整个命名空间中的所有内容引入到当前作用域中,这可能会导致命名冲突。

如在此题中,不可以定义max变量名。

你可能感兴趣的:(算法)