ACM题解——动态规划专题——“Help Jimmy“

ACM题解——动态规划专题——"Help Jimmy" 

题目描述

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

场景中包括多个长度和高度各不相同的平台。地面是最低的平台,高度为零,长度无限。

Jimmy老鼠在时刻0从高于所有平台的某处开始下落,它的下落速度始终为1米/秒。当Jimmy落到某个平台上时,游戏者选择让它向左还是向右跑,它跑动的速度也是1米/秒。当Jimmy跑到平台的边缘时,开始继续下落。Jimmy每次下落的高度不能超过MAX米,不然就会摔死,游戏也会结束。

设计一个程序,计算Jimmy到底地面时可能的最早时间。

 

Input

第一行是测试数据的组数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恰好落在某个平台的边缘,被视为落在平台上。所有的平台均不重叠或相连。测试数据保证问题一定有解。

 

Output

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

 

Sample Input

1
3 8 17 20
0 10 8
0 10 13
4 14 3

Sample Output

23

题意

Jimmy在x位置,y高度,向下跳下面不同的位置、不同的高度共有p块挡板,挡板均不重叠,Jimmy可以在挡板上向左向右走,然后从边上向下跳,但是每次跳跃的高度最多不可以超过maxH,每向下或者向左向右一格就花费1s,求最后落地所花的最短时间。

题解

分析这个题目发现对于一块板子来说,Jimmy从一个位置(x,y)落到板子上,要么从左边下去,要么从右边下去,设两个数组dp_left[n+1],和dp_right[n+1]分别每块板子分别从其左边和右边到达地面的最短时间。当你从上往下记录是可以发现上面的决定可能会影响到之后,当前最右并不是全局最优,因此要从后面来开始分析;说道这里前后的划分是怎么来的呢,是到达板子的时间,换句话说就是板子的高度,因为板子不会重合,所以一个位置上只有一块板子;将板子按照高度从小到大排,那么从后面的板子开始分析,记录其从板子的最左边和最右边的到达地面最短时间:当这个板子的高度到达地面的高度小于maxH时,就可以直接跳下去,这个最短时间就是板子高度到地面花费时间;若是不能够抵达地面的,那么就要从比当前板子低的里面寻找可以落到的,且高度差不大于maxH的,当前左边沿位置就是Jimmy当前的位置点,左边的最小落地时间就等于minn(左边下一块板子的左边落地最小值+左边下一块左端距离当前板子左边沿,左边下一块板子右边落地最小值+右边下一块板子右端距离板子左边沿)+本块板子与左边下一块板子的距离,代表的含义是从当前这块板子到达下面可到达的第一块符合要求的板子,可以从这块板子左边或者右边下去,选择最小值再加上高度就是从当前板子左端下去到地面的最小时间;当找不到可以落的板子,也无法落地的,最短时间就赋为无穷大,代表是死路无法抵达地面,因为它一定不会是答案路径的一部分。右边做重复操作即可。最后分析到最高的板子,也就是起始位置(把它当做一块左边沿为x,右边沿为x,高度为y的板子),输出dp_left[0],dp_right[0]的最小值即是答案。

  做个图:                        

所以状态方程为:dp_l_e_f_t[i]=min\left ( dp_l_e_f_t[i+1]+pf[i].left-pf[i+1].left,dp_r_i_g_h_t[i+1]+pf[i+1].right-pf[i].right\right ))+pf[i].height-pf[i+1].height+pf[i].height-pf[i+1].height

代码

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
#define inf 0x3f3f3f3f;
struct pf
{
    int l,r,height;
};
int minn(int x,int y)
{
    if(x<y)
        return x;
    else
        return y;
}
bool cmp(pf x,pf y)
{
    if(x.height==y.height)
        return x.l<y.l;
    else
        return x.height>y.height;
}
int main()
{
    int t=0;
    cin>>t;
    for(int i=0;i<t;i++)
    {
        int n=0,x=0,y=0,maxH=0;
        cin>>n>>x>>y>>maxH;
        //cout<<n<<" "<<x<<" "<<y<<" "<<maxH<<endl;
        pf *p=new pf[n+1];
        for(int j=0;j<n;j++)
            cin>>p[j].l>>p[j].r>>p[j].height;
        p[n].l=p[n].r=x;                //把起始位置补成最高的板子
        p[n].height=y;
        sort(p,p+n+1,cmp);
        int *dp_left=new int[n+1];      //板子从左到地的最短时间+上一个位置到该块板
        int *dp_right=new int[n+1];     //板子从右到地的最短时间
        memset(dp_left,0,sizeof(dp_left));
        memset(dp_right,0,sizeof(dp_right));
        for(int j=n;j>=0;j--)
        {
            if(j==n && p[j].height<=maxH)
            {
                dp_left[j]=p[j].height;
                dp_right[j]=p[j].height;
            }
            for(int k=j+1;k<=n;k++)
            {
                if(p[j].r>=p[k].l && p[j].r <=p[k].r && p[j].height-p[k].height<=maxH)
                {
                    dp_right[j]=minn(p[j].r-p[k].l+dp_left[k],p[k].r-p[j].r+dp_right[k])+p[j].height-p[k].height;
                    break;           //找到合适的板子就不再找了,下面也是一样的
                }
                else if(k==n && p[j].height<=maxH)
                    dp_right[j]=p[j].height;
                else if(k==n && p[j].height>maxH)
                    dp_right[j]=inf;
            }
            for(int k=j+1;k<=n;k++)
            {
                if(p[j].l>=p[k].l && p[j].l <=p[k].r && p[j].height-p[k].height<=maxH)
                {
                    dp_left[j]=minn(p[j].l-p[k].l+dp_left[k],p[k].r-p[j].l+dp_right[k])+p[j].height-p[k].height;
                    break;
                }
                else if(k==n && p[j].height<=maxH)
                    dp_left[j]=p[j].height;
                else if(k==n && p[j].height>maxH)
                    dp_left[j]=inf;
            }

        }
        cout<<minn(dp_left[0],dp_right[0])<<endl;
    }
    return 0;
}

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值