NOIP 2007普及组 守望者的逃离 DP详解

题目

描述

恶魔猎手尤迪安野心勃勃,他背叛了暗夜精灵,率领深藏在海底的娜迦族企图叛变。守望者
在与尤迪安的交锋中遭遇了围杀,被困在一个荒芜的大岛上。为了杀死守望者,尤迪安开始对这
个荒岛施咒,这座岛很快就会沉下去。到那时,岛上的所有人都会遇难。守望者的跑步速度为
17m/s,以这样的速度是无法逃离荒岛的。庆幸的是守望者拥有闪烁法术,可在1s内移动60m,不
过每次使用闪烁法术都会消耗魔法值10点。守望者的魔法值恢复的速度为4点/s,只有处在原地
休息状态时才能恢复。
现在已知守望者的魔法初值M,他所在的初始位置与岛的出口之间的距离S,岛沉没的时间T。

你的任务写写一个程序帮助守望者计算如何在最短的时间内逃离荒岛,若不能逃出,则输出守望
者在剩下的时间能走的最远距离。注意:守望者跑步、闪烁或休息活动均以秒(s)为单位,且每
次活动的持续时间为整数秒。距离的单位为米(m)。
格式
输入格式

在输入文件escape.in仅一行,包括空格隔开的三个非负整数M,S,T。
输出格式

在输出文件escape.out包括两行:
第1行为字符串“Yes”或“No”(区分大小写),即守望者是否能逃离荒岛。
第2行包含一个整数。第一行为“Yes”(区分大小写)时表示守望者逃离荒岛的最短时间;
第一行为“No”(区分大小写)时表示守望者能走的最远距离。
样例1
样例输入1

39 200 4

样例输出1

No
197

样例2
样例输入2

36 255 10

样例输出2

Yes
6

限制

1s
提示

30%的数据满足:1<=T<=10,1<=S<=100
50%的数据满足:1<=T<=1000,1<=S<=10000
100%的数据满足:1<=T<=300000,0<=M<=1000,1<=S<=10^8

思路

这道题可以用贪心和DP两种解法求解,这里先讲讲DP

首先,显然循环枚举枚举距离是不现实的,而题目中说所有操作都是以秒为时间单位进行的,因此循环时间进行DP

其次,相信有很多coder在做这道题的时候卡在状态转移方程的判断上,因为具有跑,闪现,停下来补魔法值3种选择。针对这种情况,把他们放在同一个循环内解决是不太现实的。所以,我们使用两个循环分开解决,第一个循环只考虑使用闪现魔法,第二个循环再用朴素的跑步来进行优化。

AC代码

#include 
#include 
#include 
#define MAXT 300001
using namespace std;

int magic,dis,tim,ans;
int f[MAXT];  //f[i]表示当时间为i时的最优解 
bool flag;    //flag表示能否逃出荒岛 

void dp()  //核心代码,dp 
{
    for (int i=1;i<=tim;i++){  //这个循环先处理不停闪现的情况,原则是有魔法就闪,没有就停下来补 
        if (magic>=10){        //够魔法值,进行闪现 
            f[i]=f[i-1]+60;
            magic-=10;
        }else{      //不够魔法值,停下来补充 
            f[i]=f[i-1];
            magic+=4;
        }
    }

    for (int i=1;i<=tim;i++){  //对于所有时间,看是否能用普通跑步来优化 
        f[i]=max(f[i],f[i-1]+17);
    }
}

void find_ans()  //找到答案(不重要) 
{
    for (int i=1;i<=tim;i++){
        if (f[i]>=dis){
            flag=true;
            ans=i;
            break;
        }
        if (i==tim){
            flag=false;
            ans=f[i];
        }
    }

    if (flag){
        printf("Yes\n%d\n",ans);
    }else{
        printf("No\n%d\n",ans);
    }
}

int main()
{
    memset(f,0,sizeof(f));
    scanf("%d %d %d",&magic,&dis,&tim);
    dp();
    find_ans();
    return 0;
}

你可能感兴趣的:(题目解析,动态规划,noip)