mooc北大算法课第七周:动态规划(二)

本文介绍了动态规划在解决实际问题中的应用,包括帮助Jimmy找到到达地面的最早时间以及找到滑雪区域中最长的滑坡。通过对问题的分析,将问题分解为子问题,并通过递归公式求解,最终得出解决方案。动态规划方法在这些问题中展现出强大的求解能力。
摘要由CSDN通过智能技术生成

现在就是最好的时刻!终于写完啦✍

动态规划2

例题1:help jimmy
描述:
"help jimmy"场景中包含多个长度和高度各不相同的平台。地面是最低的平台,高度为0,长度无限。Jimmy老鼠在时刻0从高于所有平台的某处开始下落,他的下落速度始终为1m/s。当Jimmy落在某个平台上时,游戏者选择让它向左还是向右跑,它跑动的速度也是1m/s。当Jimmy跑到平台的边缘时,开始继续下落。Jimmy每次下落的高度不能超过MAX米,不然就会摔死,游戏也会结束。

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

解题思路:
Jimmy跳到一块板上后,可以有两种选择,向左走,或向右走。走到左端和走到右端所需的时间,是很容易算的。

如果我们能知道以左端为起点到达地面的最短时间,和以右端为起点到达地面的最短时间,那么向左走还是向右走,就很容易选择了。

因此,整个问题就被分解为两个子问题,即Jimmy所在位置下方第一块板左端为起点到地面的最短时间,和右端为起点到地面的最短时间。

这两个子问题在形式上和原问题是完全一致的。将板子从上到下从1开始进行无重复的编号(越高的板子编号越小,高度相同的几块板子,哪块编号在前无所谓,那么和上面两个子问题相关的变量只有板子编号。

不妨认为Jimmy开始的位置是一个编号为0,长度为0的板子,假设LeftMinTime(k)表示从k号板子左端到地面的最短时间,RightMinTime(k)表示从k号板子右端到地面的最短时间,那么,求板子k左端点到地面的最短时间的方法如下:

if(板子左端正下方没有别的板子) {
if(板子k的高度h(k)大于MAX)
LeftMinTime(k) = ∞;
else
LeftMinTim(k) = h(k);// 直接跳到地上
}
else if(板子k左端正下方的板子编号是m) {// 落到板子m的时间(高度差)+下一步 (左/右端时间 + 走到左/右端的时间)
LeftMinTime(k) = h(k) - h(m) + Min(LeftMinTime(m) + Lx(k) - Lx(m), RightMinTime(m) + Rx(m) - Lx(k));
}
// RightMinTime(k)求解过程类似。不妨认为Jimmy开始的位置是一个编号为0,长度为0的板子,那么整个问题就是要求LeftMinTime(0)
// 输入数据中,板子并没有按高度排序,所以程序一定要首先将板子排序。
// 时间复杂度:一共n个板子,每个左右两端的最小时间各算一次O(n),找出板子一段到地面之间的那块板子,需要遍历板子O(n),总的时间复杂度O(n^2)

输入:
第一行是测试数据的组数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到底地面时可能的最早时间。

输入样例:

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

输出样例:

23

代码:
援引:https://www.cnblogs.com/Renyi-Fan/p/6960829.html

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define MAX 1000
#define INFINITE 1000000
int t,n,x,y,maxHeight;
// t代表输入数据组数,n代表平台数量,x代表横坐标,y代表纵坐标,maxHeight代表一次下落最大高度
struct Platform{
   //定义平台结构体
    int Lx, Rx, h;//Lx表示左边界横坐标,Rx表示右边界横坐标,h表示高度
    bool operator < (const Platform & p2) const {
   //符号重载,sort的时候能按h从高到低排
        return h > p2.h;
    }
};

Platform platForms[MAX + 10];//平台
int leftMinTime[MAX + 10];//走左边的最小时间
int rightMinTime[MAX + 10];//走右边的最小时间
//l表示现在这块板的编号,越在上面的编号越小,bleft表示是否向左边走
//因为这题分为向左和向右两种情况
int MinTime( int l, bool bLeft )//l表示现在这块板的编号,越在上面的编号越小,bleft表示是否向左边走
{
   
    //初始化x和y坐标,如果是去左边,就走到左边边上,如果去右边,就走到右边边上
    y = platForms[l].h;
    //如果是去左边,就走到左边边上,如果去右边,就走到右边边上
    if(bLeft)
        x = platForms[l].Lx;
    else
        x = platForms[l].Rx;
    int i;

    for( i = l + 1;i <= n;i ++ ) {
   //找到现在这块板下面的那块板
        if( platForms[i].Lx <= x && platForms[i].Rx >= x)//判断从当前条能跳到的小一块板子上
        break;
    }
    if( i <= n ) {
   // 板子l正下方有别的板
        if( y - platForms[i].h > maxHeight )// 跳到这块平台的高度如果大于Max
        return INFINITE;//返回无限大
    }
    else {
   // 板子l正下方没有别的板
        if( y > maxHeight )//板子l的高度 h(k) 大于Max
            return INFINITE;//返回无限大
        else
            return y;//如果可以直接跳下,就输出y
    }

    int nLeftTime = y - platForms[i].h + x - platForms[i].Lx;//现在平台与下一块平台的高度差以及下一块平台左边界的距离
    int nRightTime = y - platForms[i].h + platForms[i].Rx - x;//现在平台与下一块平台的高度差以及下一块平台右边界的距离

    if( leftMinTime[i] == -1 ) //等于-1表示我初始化过 ,如果还可以向左我们就向左
        leftMinTime[i] = MinTime(i,true);//向左进入子问题
    if( rightMinTime[i] == -1 )//等于-1表示我初始化过 ,如果还可以向右我们就向右
        rightMinTime[i] = MinTime(i,false);//像右进入子问题
    nLeftTime += leftMinTime[i];//左边固定花费的时间加上下一场左边这样的时间
    nRightTime += rightMinTime[i];//右边固定花费的时间加上下一场右边这样的时间
    //返回左边和右边走值小的那一个
    if( nLeftTime < nRightTime )
        return nLeftTime;
    return nRightTime;
}

int main() {
   
    // freopen("in.txt","r",stdin); //测试时候用一下,提交代码不用管
    scanf("%d",&t);//读入t组数据
    for( int i = 0;i < t; i ++ ) {
   //对每组数据进行操作
        memset(leftMinTime,-1,sizeof(leftMinTime));//初始化leftMinTime
        memset(rightMinTime,-1,sizeof(rightMinTime));//初始化rightMinTime
        scanf("%d%d%d%d",&n, &x, &y, &maxHe
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值