◆竞赛题目◆◇NOIP 2017◇ jump 跳房子

来源:互联网 发布:淘宝买家怎么看退款率 编辑:程序博客网 时间:2024/05/18 00:40

◇NOIP 2017◇ jump 跳房子


Description
跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一。跳房子的游戏规则如下:
在地面上确定一个起点,然后在起点右侧画n 个格子,这些格子都在同一条直线上。每个格子内有一个数字(整数),表示到达这个格子能得到的分数。玩家第一次从起点开始向右跳,跳到起点右侧的一个格子内。第二次再从当前位置继续向右跳,依此类推。规则规定:
玩家每次都必须跳到当前位置右侧的一个格子内。玩家可以在任意时刻结束游戏,获得的分数为曾经到达过的格子中的数字之和。
现在小R 研发了一款弹跳机器人来参加这个游戏。但是这个机器人有一个非常严重的缺陷,它每次向右弹跳的距离只能为固定的d。小R 希望改进他的机器人,如果他花g 个金币改进他的机器人,那么他的机器人灵活性就能增加g,但是需要注意的是,每次弹跳的距离至少为1。具体而言,当g < d时,他的机器人每次可以选择向右弹跳的距离为d-g, d-g+1,d-g+2,…,d+g-2,d+g-1,d+g;否则(当g ≥ d时),他的机器人每次可以选择向右弹跳的距离为1,2,3,…,d+g-2,d+g-1,d+g。
现在小R 希望获得至少k 分,请问他至少要花多少金币来改造他的机器人。

Input
第一行三个正整数n,d,k,分别表示格子的数目,改进前机器人弹跳的固定距离,以及希望至少获得的分数。相邻两个数之间用一个空格隔开。
接下来n 行,每行两个正整数
Output
共一行,一个整数,表示至少要花多少金币来改造他的机器人。若无论如何他都无法获
得至少k 分,输出-1。

Sample Input

7 4 102 65 -310 311 -313 117 620 2

Sample Output

2

◆题目解析◆
最后一题的确有难度……正式考试成绩发下来也才50分,RE 或者 TLE。用了正确的思路 —— 二分查找+动态规划判断 ,可惜没有优化 (-_-!)。动态规划是用的 记忆化搜索 的模板,但是明显栈空间爆炸,考试的时候就发现了。那么正解是要加上 单调队列 的优化,而且……不能递归。
(根据作者的理解)单调队列主要是有一个空间的优化——将没有任何用处的元素在入队时就删去了。
下面这个程序是作者优化后的,我们可以用 STL 容器——双端队列 deque<> 来实现单调队列(或者手写一个单调队列,速度更快,但是作者并不会写 哼(ˉ(∞)ˉ)唧)。这道题能用单调队列的原因是

之前的元素如果比现在要加入的元素小,它就没有任何的意义了

否则就不能使用单调队列优化。
先讲一讲总体的思路—— ①二分查找:由题目可以推导出答案的最大值 r 和最小值 l ,并且所得分数的增大和减小直接对应了答案的增大和减小,在区间 [l,r] 中查找最佳答案的算法即是二分查找;②动态规划:由于二分查找已经确定了题目中 g 的值,因此我们可以求出机器人能跳跃到的区间,求一个序列中满足一定条件的子序列最大值最优算法为动态规划。
我们不难发现,只要所有为正数的格子中的数字的和大于要求的最小值,则一定可以满足——只要 g 足够大,就可以只选择跳到数字为正数的格子上。反之就不可以!根据这一点,我们可以先进行特判(其实在作者的程序里是必须特判,作者也不知道为什么)。
接下来进行二分查找——左边界固定为0,右边界尽可能大,但是比较合适的是定义为最远的格子的距离——因为机器人跳得再远也不可能跳到最远的格子的后面!我们暂时设检测 g 是否满足的函数为 check() ,传入2个参数 closest,farthest 表示机器人能跳跃的距离是区间 [closest,farthest] 。最后输出的是得到的右边界。
由于这里每个格子包含2个值,动态规划的存储状态的数组也包含2个值,这里使用 deque<> 的话就必须使用结构体作为变量类型(其实也可以用 STL 容器 pair<>,但是结构体更直观)。定义存储状态的数组为 Check[i] 表示前 i 个格子所的的最大分数,结构体数组 block[i] 包含格子离起点的距离 far 、格子的数字 value,状态转移方程式如下:

Check[i]=max{Check[j]}+block[i]value
block[j]far+closestblock[i]farblock[j]far+farthest

这里我们看到是要找最大值——即单调下降队列。那么 max{Check[j]} 就是单调队列的队首。入队是比较一般的单调队列操作,而维护这个队列还要通过去除队首来避免选择到已经在机器人跳跃范围外的格子,即设队首为 front 则若其不满足 frontfar+closestblock[i]farfrontfar+farthest ,就去除队首,直到队首满足条件或队列为空。这样对整个单调队列的维护就完毕了。此时单调队列有可能为空,也就是当前机器人不能从任何一个格子跳到该格子,那么我们将它的分数定义为最小值(109足够小了)以避免选择。


◆AC 程序◆

/*Lucky_Glass*///Problem 4#include<cstdio>#include<queue>#include<algorithm>using namespace std;struct BLOCK{int far,value;}block[500005];struct Deque{int far,value;}Push;int Num_point,F,want,G;bool check(int closest,int farthest){    deque<Deque> que;    int Check[500005]={},quepush=0;    Push.far=Push.value=0;    for(int i=1;i<=Num_point;i++)    {        while(block[quepush].far+closest<=block[i].far && quepush<i)        {            Push.far=block[quepush].far,Push.value=Check[quepush];            while(!que.empty() && que.back().value<Push.value)                que.pop_back();            que.push_back(Push);            quepush++;        }        while(que.front().far+farthest<block[i].far && !que.empty()) que.pop_front();        if(!que.empty()) Check[i]=que.front().value+block[i].value;        else Check[i]=-1e9;        if(Check[i]>=want) return true;    }    return false;}int main(){    freopen("jump.in","r",stdin);    freopen("jump.out","w",stdout);    scanf("%d%d%d",&Num_point,&F,&want);    long long sum=0;    for(int i=1;i<=Num_point;i++) scanf("%d%d",&block[i].far,&block[i].value),sum+=(block[i].value>0)? block[i].value:0;    if(sum<want){printf("%d\n",-1);return 0;}    int left=0,right=block[Num_point].far;    while(left+1<right)    {        G=(left+right)/2;        if(check(G<=F? F-G:1,F+G))            right=G;        else            left=G;    }    printf("%d\n",right);    return 0;}

The End

Thanks for reading!

-Lucky_Glass


原创粉丝点击