『Algorithm』守望者的逃离-解题报告

题目描述

Vijos链接-守望者的逃离

恶魔猎手尤迪安野心勃勃,他背叛了暗夜精灵,率领深藏在海底的娜迦族企图叛变。守望者
在与尤迪安的交锋中遭遇了围杀,被困在一个荒芜的大岛上。为了杀死守望者,尤迪安开始对这
个荒岛施咒,这座岛很快就会沉下去。到那时,岛上的所有人都会遇难。守望者的跑步速度为
17m/s,以这样的速度是无法逃离荒岛的。庆幸的是守望者拥有闪烁法术,可在1s内移动60m,不
过每次使用闪烁法术都会消耗魔法值10点。守望者的魔法值恢复的速度为4点/s,只有处在原地
休息状态时才能恢复。
现在已知守望者的魔法初值M,他所在的初始位置与岛的出口之间的距离S,岛沉没的时间T。
你的任务写写一个程序帮助守望者计算如何在最短的时间内逃离荒岛,若不能逃出,则输出守望
者在剩下的时间能走的最远距离。注意:守望者跑步、闪烁或休息活动均以秒(s)为单位,且每
次活动的持续时间为整数秒。距离的单位为米(m)。

几句闲谈

这几天正在为ACM市赛作准备,突发奇想回到了Vijos刷这道初中就留下了深刻记忆的题目…
好几番挣扎之后终于AC了,普及组的水平对于大二的我来说可能仍然不容易吧
『Algorithm』守望者的逃离-解题报告_第1张图片

解题思路

  • 这是一道主要考察贪心的题目(用动态规划思路应该也没问题,但是感觉难很多我也写不出来…)
  • 题目中给的数据其实很精妙,有一点尤其关键:
    20点魔法需要5秒的时间恢复,可以支撑2次闪烁,即花费7秒行进60*2=120米
    而7秒如果全部都在跑步的话,只能行进7*17=119米
  • 119米与120米是解题的关键所在
    可以看到,7秒钟全闪烁和全跑步,相差路程只差了1米,闪烁并不比跑步有悬殊上的优越
    所以必须在足够“安全”的情况下使用闪烁
  • 情况1:如果魔法剩余量为10及以上,不用想太多直接闪烁即可
    情况2:如果魔法剩余量在[6,10]区间,可以原地回魔1秒再闪烁,前提:剩余时间>=2、距离>=34
    情况3:如果魔法剩余量在[2,5]区间,可以原地回魔2秒再闪烁,前提:剩余时间>=3、距离>=51
    情况4:如果魔法剩余量在[0,1]区间,可以…打住!这种情况麻烦很多!这是本题最纠结的地方
    简单地思考可能会选择原地回魔3秒再闪烁。但是这种情况非常造成魔法过剩,之前证明过闪烁并不比跑步有悬殊上的优越,魔法过剩就意味着跑步优于闪烁。
  • 第一第二第三种情况应为足够“安全”,即比跑步肯定是要划算的,所以无需考虑魔法过剩的情况
    试想在某一次逃离中,守望者连续闪烁了若干次,此时离出口很近了,闪烁一次就到了,而逃离状态符合情况4。此时该不该闪烁呢?
    仔细思考一下不难明白,如果这一次抉择是发生在一连串闪烁的最后一次,选择完全跑下去而不是闪烁一次再跑绝对是更明智的。恢复魔法+闪烁需要4秒,3秒内可以行进60米,而这4秒直接跑路的话可以走68米,所以最后一次闪烁抉择,如果正好遇到情况四,那还是果断直接跑路吧
  • 守望者的逃离大致分为两个阶段:
    1. 第一个阶段,因为初始状态中含有一定量的魔法,可以支撑若干次支撑,所以可以连续闪烁。此时注意判断是否在阶段中时间就到了或者出口就到了。
    2. 第二个阶段,恢复一下闪烁一下,如此循环往复,然后在临近出口时选择好开始直接跑路的时机
      这个阶段同样要注意监测好时间是否到了。

AC代码

package com.company;

import java.util.Scanner;

public class Main {

    public static int specialDevide(int num1,int num2) {
        if (num1 % num2 == 0){
            return num1 / num2;
        }else{
            return num1 / num2 + 1;
        }
    }

    public static void print(String s,int num){
        System.out.println(s + "\n" +num);
        System.exit(0);
    }

    public static boolean canBlinkTwice(int timeLimit,int nowMagic,int distance){
        int t = specialDevide(20 - nowMagic , 4);
        int t2 = specialDevide(distance, 60);
        if(t + 2 <= timeLimit && t2 >= 2){
            return true;
        }else{
            return false;
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int magic = sc.nextInt();
        int distance = sc.nextInt();
        int timeLimit = sc.nextInt();
        int t = timeLimit;//备份源数据
        int dis = distance;//备份源数据
        //阶段1:连续闪烁
        int n = magic / 10;
        int d = n * 60;
        //情况1.1:阶段1中间,时间用尽,而且没有逃出
        if (timeLimit < n && d < distance){
            print("No", timeLimit * 60);
        }
        //情况1.2:阶段1中间,已经逃出了——>(时间用尽,不过已经逃出了)||(时间未用尽,不过已经逃出了)
        if ((t * 60 >= distance && timeLimit <= n) || (d >= distance && timeLimit >= n)){
            print("Yes", specialDevide(distance, 60));
        }
        //情况1.3:阶段1中间,时间未用尽
        magic -= n * 10;
        timeLimit -= n;
        distance -= d;
        //阶段2:连续休息+闪烁,最后跑步
        for (;;){
            if (distance <= 0) print("Yes",t - timeLimit);//时间未到,已经逃脱
            if (timeLimit == 0) print("No",dis - distance);//时间已到,没有逃脱
            int recoverTime = specialDevide(10 - magic, 4);
            //System.out.print("魔法:" + magic + "距离:" + distance + "时间:" + timeLimit);
            if (magic >= 6 && distance >= 34 && timeLimit >= 1){//休息1秒再闪烁
                //System.out.println("休息1秒再闪烁");
                timeLimit -= 2;
                distance -= 60;
                magic = magic + 4 - 10;
                continue;
            }
            if (magic >= 2 && distance >= 51 && timeLimit >= 2){//休息2秒再闪烁
                //System.out.println("休息2秒再闪烁");
                timeLimit -= 3;
                distance -= 60;
                magic = magic + 8 - 10;
                continue;
            }
            if (timeLimit >= 7 && distance >= 120){
                //System.out.println("闪烁闪烁");
                timeLimit -= 7;
                distance -= 120;
                continue;
            }
            //System.out.println("直接跑路");
            timeLimit -= 1;
            distance -= 17;
        }
    }
}

失败过程

用递归写的,没有考虑好情况4,数据大了会爆栈也是个大问题

import java.util.Scanner;

public class Main {

    public static int getTime(int magic,int distance,int timeLimit){
        //已经逃离出去了
        if (distance < 0){
            return 0;
        }
        //魔法有余
        if (magic >= 10 && timeLimit >= 1) {
            //System.out.println("直接闪烁");
            return 1 + getTime(magic - 10,distance - 60,timeLimit - 1);
        }
        //魔法不足但是时间足够,而且闪烁比跑步划算
        int t = timeToRegerateMagic(magic);
        if (timeLimit >= t + 1 && t + 1 <= timeToRunToEnd(distance)) {
            //System.out.println("先休息" + t + "秒,再闪烁");
            return t + 1 + getTime(magic + t * 4 - 10,distance - 60,timeLimit - t - 1);
        }
        //时间不够闪烁了
        if (timeLimit >= 1) {
            //System.out.println("跑步");
            return 1 + getTime(magic,distance - 17,timeLimit - 1);
        }
        //时间不够1s
        System.out.println("No\n" + (d - distance));
        System.exit(0);
        return -99999;
    }

    public static int d;
    public static int t;

    public static int timeToRegerateMagic(int magic){
        if ((10 - magic) % 4 == 0){
            return (10 - magic) / 4;
        }else{
            return (10 - magic) / 4 + 1;
        }
    }

    public static int timeToRunToEnd(int distance){
        if (distance % 17 == 0){
            return distance / 17;
        }else{
            return distance / 17 + 1;
        }
    }

    public static void main(String[] args) {
    // write your code here
        Scanner sc = new Scanner(System.in);
        int magic = sc.nextInt();
        int distance = sc.nextInt();
        int timeLimit = sc.nextInt();
        d = distance;
        t = timeLimit;
        System.out.println("Yes\n" + getTime(magic, distance, timeLimit));

    }
}

当时错了4个数据点但不知道错哪了,无奈之下从网上苦苦找到了Vijos的测试数据
看了测试数据后…好吧,没办法了只能重构咯
第二次代码改进了结构,对于情况4也有了一定的考虑,考虑的很细但是仍然无法完美解决问题
最优的时候也错了2个数据点

import java.util.Scanner;

public class Main {

    public static int specialDevide(int num1,int num2) {
        if (num1 % num2 == 0){
            return num1 / num2;
        }else{
            return num1 / num2 + 1;
        }
    }

    public static void print(String s,int num){
        System.out.println(s + "\n" +num);
        System.exit(0);
    }

    public static boolean canBlinkTwice(int timeLimit,int nowMagic,int distance){
        int t = specialDevide(20 - nowMagic , 4);
        int t2 = specialDevide(distance, 60);
        if(t + 2 <= timeLimit && t2 >= 2){
            return true;
        }else{
            return false;
        }
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int magic = sc.nextInt();
        int distance = sc.nextInt();
        int timeLimit = sc.nextInt();
        int t = timeLimit;//备份源数据
        int dis = distance;//备份源数据
        //阶段1:连续闪烁
        int n = magic / 10;
        int d = n * 60;
        //情况1.1:阶段1中间,时间用尽,而且没有逃出
        if (timeLimit < n && d < distance){
            print("No", timeLimit * 60);
        }
        //情况1.2:阶段1中间,已经逃出了——>(时间用尽,不过已经逃出了)||(时间未用尽,不过已经逃出了)
        if ((t * 60 >= distance && timeLimit <= n) || (d >= distance && timeLimit >= n)){
            print("Yes", specialDevide(distance, 60));
        }
        //情况1.3:阶段1中间,时间未用尽
        magic -= n * 10;
        timeLimit -= n;
        distance -= d;
        //阶段2:连续休息+闪烁,最后跑步
        for (;;){
            int recoverTime = specialDevide(10 - magic, 4);
            //作出判断,这次是闪烁还是跑步
            //何时应该闪烁?
            //第一,只要不是最后一次闪烁选择,而且比跑步划算,就可以闪烁
            //第二,如果是最后一次闪烁,此时魔法量>=2,而且比跑步划算,而且时间充裕,就可以闪烁
            if (canBlinkTwice(timeLimit,magic,distance) || ( !canBlinkTwice(timeLimit,magic,distance) && (magic >= 2) && (timeLimit > recoverTime + 1) ) ){//闪烁比较划算
                //System.out.println("还剩" + magic +"点魔法,休息" + recoverTime + "秒再闪烁");
                timeLimit -= recoverTime + 1;
                magic = magic + recoverTime * 4 - 10;
                distance = distance - 60;
            }else{//直接跑路比较划算
                //System.out.println("直接跑路");
                timeLimit -= 1;
                distance = distance -17;
            }
            if (distance <= 0) print("Yes",t - timeLimit);//时间未到,已经逃脱
            if (timeLimit == 0) print("No",dis - distance);//时间已到,没有逃脱
        }
    }
}

当时感觉魔法过剩的问题太麻烦了,完全没法改(实质是没有思考清楚)
感觉自己已经无力解决,无奈之下百度参考了下别人的题解,遂思路清晰迅速AC掉了…

一点感悟

这道题大概是我做的第一道能算贪心的题吧,感觉对自己来说还是很难的,所以写了一下解题报告…
如果放在ACM的规则下那考试时肯定是没办法AC的…
通过这道题了解到了什么样的题算贪心,以及…充分训练了debug断点调试能力…

你可能感兴趣的:(Algorithm)