Help Jimmy(动态规划)--算法学习

题意

“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<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define INF 0x3f3f3f3f
int n, m;
int dp[1010][2];
struct st//定义木板结构体,x1为左端点,x2为右端点,h为高度
{
    int x1, x2, h;
}a[1010];
//定义排序方式,按照木板的高度排序,从小到大排序,下面的为1号
bool cmp(st a, st b)
{
    return a.h < b.h;
}
//从左边
void left(int i)
{
    int k = i - 1;//i,k是木板的编号
    while (k > 0 && a[i].h - a[k].h <= m)//当两个木板之间的距离小于max时运行
    {   //比较从上一个木板掉下来往左走的距离和忘有走的距离那个少
        if (a[k].x1 <= a[i].x1 && a[k].x2 >= a[i].x1)
        {
            dp[i][0] = a[i].h - a[k].h + min(dp[k][0] + a[i].x1 - a[k].x1, dp[k][1] + a[k].x2 - a[i].x1);
            return;
        }
        else k--;
    }
    //最后一个木板,如果距离地面距离小于max就直接跳下去,如果大于max就说明下不去
    if (a[i].h - a[k].h <= m)dp[i][0] = a[i].h;
    else dp[i][0] = INF;
}
//从右边
void right(int i)
{
    int k = i - 1;
    while (k > 0 && a[i].h - a[k].h <= m)
    {
        if (a[k].x1 <= a[i].x2 && a[k].x2 >= a[i].x2)
        {
            dp[i][1] = a[i].h - a[k].h + min(dp[k][0] + a[i].x2 - a[k].x1, dp[k][1] + a[k].x2 - a[i].x2);
            return;
        }
        else k--;
    }
    if (a[i].h - a[k].h <= m)dp[i][1] = a[i].h;
    else dp[i][1] = INF;
}
int main()
{
    int t;
    cin >> t;
    while (t--)
    {
        memset(a, 0, sizeof(a));
        int x, y;
        cin >> n;
        cin >> x; cin >> y; cin >> m;
        for (int i = 1; i <= n; i++)
        {
            cin >> a[i].x1; cin >> a[i].x2; cin >> a[i].h;
        }
        a[n + 1].x2 = a[n + 1].x1 = x;
        a[n + 1].h = y;
        a[0].x1 = -20000;
        a[0].x2 = 20000;
        a[0].h = 0;
        sort(a, a + n + 1, cmp);//根据木板的高度进行排序
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= n + 1; i++)
        {
            left(i);
            right(i);
        }
        cout << min(dp[n + 1][0], dp[n + 1][1]) << endl;//输出距离小的那个,0代表从左边,1代表右边
    }
    return 0;
}
发布了95 篇原创文章 · 获赞 184 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/weixin_45822638/article/details/105092917
今日推荐