是男人就下100层

题目描述

  相信大家都听说过“是男人就下100层”系列游戏,游戏中包括多个长度和高度各不相同的平台,地面是最低的平台,高度为零,长度无限。
  一个男人在开始的时候从高于所有平台的某处开始下落,它的下落速度始终为1米/秒。当他落到某个平台上时,游戏者选择让他向左或向右跑,跑动的速度也是1米/秒。当他跑到平台的边缘时会继续下落。要求每次下落的高度不能超过MAX米,不然就会摔死,游戏也会结束。
  请帮忙设计一个程序,计算最快到达地面所用的时间。

1.1 输入描述:

  输入包含多组数据。
  每组测试数据的第一行是四个整数N、X、Y、MAX,用空格分隔。N是平台的数目(不包括地面),X和Y是游戏开始时男人所在位置的坐标,MAX是一次下落的最大高度。
  紧接着有N行,每行描述一个平台的信息,包括三个整数,X1[i],X2[i]和H[i]。H[i]表示平台的高度,X1[i]和X2[i]表示平台左右端点的横坐标。
  1 ≤ N ≤ 1000;-20000 ≤ X, X1[i], X2[i] ≤ 20000;1 ≤ H[i] < Y ≤ 20000(1≤i≤N)。所有坐标的单位都是米。
  平台的厚度忽略不计,如果恰好落在某个平台的边缘,被视为落在平台上。所有的平台均不重叠或相连。测试数据保证问题一定有解。

1.2 输出描述:

  对应每一组输入,输出一个整数,为到达地面最早的时间。

1.3 输入例子:

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

1.4 输出例子:

23
  •  

2 解题思路


这里写图片描述 
  图1 下100层最短走法 
  将起点看作是宽度为1的一层,这样和其它层统一处理。而从任意一层(由下向上倒数第二层以上的任意层)到最底层的最小距离为。 
  1) 直接从起点跳到最底层。 
  2) 从起点,跳到下一层,然后从左边缘走到最底层。 
  3) 从起点,跳到下一层,然后从右边缘走到最底层。 
  上述三种情况的最小距离,即为从某层到地面时的最小距离。初始化第一层下落的最小距离,然后递推,一直到起跳点可得答案。
  

3 算法实现

import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

/**
 * Declaration: All Rights Reserved !!!
 */
public class Main {
    // 坐标类
    private static class Coordinate {
        // 横坐标左端点
        int a;
        // 横坐标右端点
        int b;
        // 纵坐标
        int y;

        Coordinate(int a, int b, int y) {
            this.a = a;
            this.b = b;
            this.y = y;
        }
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            int n = scanner.nextInt();
            int x = scanner.nextInt();
            int y = scanner.nextInt();
            int max = scanner.nextInt();
            Coordinate[] cos = new Coordinate[n + 1];
            for (int i = 0; i < n; i++) {
                cos[i] = new Coordinate(scanner.nextInt(), scanner.nextInt(), scanner.nextInt());
            }

            // 最后人的人的坐标视为一个长度为0的档板
            cos[n] = new Coordinate(x, x, y);
            System.out.println(earliestTime(cos, max));

        }

        scanner.close();

    }

    /**
     * 下100层的最少时间
     *
     * @param cos 平台数组
     * @param max 人一次可以下落的最大高度
     * @return 最短的时间
     */
    private static int earliestTime(Coordinate[] cos, int max) {

        // 按离地面的高度从小到大排序
        Arrays.sort(cos, new Comparator<Coordinate>() {
            @Override
            public int compare(Coordinate o1, Coordinate o2) {
                return o1.y - o2.y;
            }
        });

        int len = cos.length;
        //  第i层到最底层的时间,0左侧下去最小时间,右侧下去的最小时间
        int[][] height = new int[len][2];

        // 第一个档板从左右到最底层的时间
        height[0][0] = cos[0].y;
        height[0][1] = cos[0].y;

        // 从下向上处理其它的档板
        for (int i = 1; i < len; i++) {
            // 假设本层到下一层高度最大
            height[i][0] = Integer.MAX_VALUE;
            height[i][1] = Integer.MAX_VALUE;

            // 本层从左右下到下一层是否已经计算过了
            boolean left = false;
            boolean right = false;

            for (int j = i - 1; j >= 0; j--) {
                // 两层的高度差
                int diff = cos[i].y - cos[j].y;

                // 高度差满足条件,并且左右两侧没有全部计算好,就继续处理
                if (diff <= max && (!left || !right)) {
                    // 从第i层的左边下去
                    if (!left && between(cos[i].a, cos[j].a, cos[j].b)) {

                        // 第j层的【左】边可达底部
                        if (height[j][0] < Integer.MAX_VALUE) {
                            // 从第i层的【左】边下去,并且通过第j层的【左】边再下去
                            height[i][0] = Math.min((cos[i].a - cos[j].a) + diff + height[j][0], height[i][0]);
                            // 第i层左边下到下一层已经处理
                            left = true;
                        }


                        // 第j层的【右】边可达底部
                        if (height[j][1] < Integer.MAX_VALUE) {
                            // 从第i层的【左】边下去,并且通过第j层的【右】边再下去
                            height[i][0] = Math.min((cos[j].b - cos[i].a) + diff + height[j][1], height[i][0]);
                            // 第i层左边下到下一层已经处理
                            left = true;
                        }
                    }

                    // 从第i层的右边下去
                    if (!right && between(cos[i].b, cos[j].a, cos[j].b)) {

                        // 第j层的【左】边可达底部
                        if (height[j][0] < Integer.MAX_VALUE) {
                            // 从第i层的【右】边下去,并且通过第j层的【左】边再下去
                            height[i][1] = Math.min((cos[i].b - cos[j].a) + diff + height[j][0], height[i][1]);
                            // 第i层右边下到下一层已经处理
                            right = true;
                        }

                        // 第j层的【右】边可达底部
                        if (height[j][1] < Integer.MAX_VALUE) {
                            // 从第i层的【右】边下去,并且通过第j层的【右】边再下去
                            height[i][1] = Math.min((cos[j].b - cos[i].b) + diff + height[j][1], height[i][1]);
                            // 第i层右边下到下一层已经处理
                            right = true;
                        }
                    }
                } else {
                    // 此处要退出内层for执行了,第i层下不到第j层
                    break;
                }
            } // 结束内层for


            // 从第i层的左边没有遇到下一层的档板,横坐标在范围内,并且从第i层直接到0层是可达的,
            // 即高度差小于max
            if (!left && checkRange(cos[i].a) && cos[i].y <= max) {
                height[i][0] = cos[i].y;
            }

            if (!right && checkRange(cos[i].b) && cos[i].y <= max) {
                height[i][1] = cos[i].y;
            }

        }

        return Math.min(height[len - 1][0], height[len - 1][1]);
    }

    /**
     * 检查横坐标是否在范围内
     *
     * @param a 模坐标
     * @return true:在范围内,false:否
     */
    private static boolean checkRange(int a) {
        return a >= -20000 && a <= 20000;
    }

    /**
     * 检查a是否在[x, y]范围内
     *
     * @param a 数值
     * @param x 起点值
     * @param y 终点值
     * @return true:在范围内,false:不在范围内
     */
    private static boolean between(int a, int x, int y) {
        return a >= x && a <= y;
    }
}
  •  

4 测试结果

这里写图片描述

5 其它信息

转载于:https://my.oschina.net/u/2822116/blog/830449

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值