"Help Jimmy" 是在下图所示的场景上完成的游戏:
场景中包括多个长度和高度各不相同的平台。地面是最低的平台,高度为零,长度无限。
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变量名。