NOIP2017普及组★跳房子

题目

跳房子

  • (jump.cpp/c/pas)
  • 2S
  • 10 * 10’
  • 传统
  • 256MB

【问题描述】

跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一。
跳房子的游戏规则如下:

在地面上确定一个起点,然后在起点右侧画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 分,请问他至少要花多少金币来改造他的机器人。

【输入格式】

输入文件名为jump.in。
第一行三个正整数n,d,k,分别表示格子的数目,改进前机器人弹跳的固定距离,以
及希望至少获得的分数。相邻两个数之间用一个空格隔开。
接下来n 行,每行两个正整数xi ,si,分别表示起点到第i个格子的距离以及第i个格子的
分数。两个数之间用一个空格隔开。保证xi按递增顺序输入。

【输出格式】

输出文件名为jump.out。
共一行,一个整数,表示至少要花多少金币来改造他的机器人。若无论如何他都无法获
得至少k 分,输出-1。

【输入输出样例1】

jump.in
7 4 10
2 6
5 -3
10 3
11 -3
13 1
17 6
20 2
jump.out
2
见选手目录下的jump/jump1.in 和jump/jump1.ans。

【输入输出样例1 说明】

花费 2 个金币改进后,小R 的机器人依次选择的向右弹跳的距离分别为2,3,5,3,4,
3,先后到达的位置分别为2,5,10,13,17,20,对应1, 2, 3, 5, 6, 7 这6 个格子。这些格
子中的数字之和15 即为小R 获得的分数。

【输入输出样例2】

jump.in
7 4 20
2 6
5 -3
10 3
11 -3
13 1
17 6
20 2
jump.out
-1
见选手目录下的jump/jump2.in 和jump/jump2.ans。

【输入输出样例2 说明】

由于样例中7 个格子组合的最大可能数字之和只有18 ,无论如何都无法获得20 分。

【输入输出样例3】

见选手目录下的jump/jump3.in 和jump/jump3.ans。

【数据规模与约定】

本题共10 组测试数据,每组数据10 分。对于全部的数据满足1 ≤ n ≤ 500000, 1 ≤ d ≤
2000, 1 ≤ xi , k ≤ 109, |si| < 105。
对于第1,2 组测试数据,n ≤ 10;
对于第3,4,5 组测试数据,n ≤ 500
对于第6,7,8 组测试数据,d = 1

分析

二分答案

显然答案有单调性,所以二分答案

有这么简单?显然作为第4题,它不会这么简单:怎么判断答案大了还是小了?

DP

肯定不能贪心,发现它完全可以DP,还是线性的。
f[i] 表示跳前 i 个格子,且停在第i个格子最大分数;
num[i] 表示第 i 个格子的分数。
于是:f[i]=max(f[j] | ji)+num[i]

这样就可以判断答案的合法性了,有这么简单,它不会这么简单,算一下时间复杂度: Θ(log2NN2) ,光是 N2 就可以让你止步在第5组数据了……

单调队列优化

所以,要优化DP……

发现转移式中的 f[j] 是随 i 的右移而右移的,即:如果每次都枚举,会重复枚举很多个f[j],如果我们把这些 f[j] 保存下来不就好了吗,所以需要用单调队列

单调队列是一个双向队列(deque),即两头都可以push或者pop等。
怎么优化呢?假设此时队列里面已经有了 f[i1] 所枚举过的 f[j] ,要得到 f[i] 所对应的 max(f[j]) ,只需要把新加入的加入,不符合条件的扔出去即可。

发现,对于格子 j ,如果j到i的距离已经大于了机器人能跳的最大距离,它就可以扔掉了,因为i是一直往右走的。

对于格子 j ,如果j i 的距离大于等于机器人能跳的最小距离,显然f[j]是可以入队的,但是在把 f[j] 放进去之前要和队列最后面的元素 f[k] 比较,如果 f[k]<f[j] f[k] 就可以直接pop掉了。

于是,这个队列中的元素一定是下降的(注意不是优先队列,优先队列是一个堆),使用时直接取front即可。

事实上,在单调队列中存下标即可。

代码

可以直接用STL中的deque(双向队列)。

#include<deque>
#include<cstdio>
#include<cstring> 
#include<algorithm>
using namespace std;
int read()
{
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}

#define MAXN 500000
int pos[MAXN+5],num[MAXN+5];
int N,D,K;
int f[MAXN+5];

bool dp_check(int g)
{
    deque<int> Q;
    memset(f,0,sizeof f);
    int Lg=max(1,D-g),Rg=D+g;
    int Now=0;//Now为现在新加入Q的格子
    //printf("%d\n",g);
    for(int i=1;i<=N;i++)
    {
        while(pos[Now]+Lg<=pos[i])
        {
            while(!Q.empty()&&f[Q.back()]<=f[Now])
            //注意,!Q.empty一定要写在前面
                Q.pop_back();
            Q.push_back(Now++);//不一定只放一个
        }
        while(!Q.empty()&&pos[Q.front()]+Rg<pos[i])//把队列前面不需要的pop掉
            Q.pop_front();
        if(!Q.empty()) f[i]=f[Q.front()]+num[i];//直接取出最前面的
        else f[i]=-0x3f3f3f3f;//否则表示到不了这个格子
        //printf("  %d\n",f[i]);
        if(f[i]>=K) return 1;//随时都有可能>=K,而不是只在循环玩后比较f[N]与K
    }
    return 0;
}

int main()
{
    freopen("jump.in" ,"r", stdin);
    freopen("jump.out","w",stdout);
    N=read(),D=read(),K=read();
    long long sum=0;
    for(int i=1;i<=N;i++)
    {
        pos[i]=read(),num[i]=read();
        if(num[i]>0) sum+=num[i];
    }
    if(sum<K)
    {
        printf("-1");
        return 0;
    }
    int left=0,right=pos[N];//答案的最大值是pos[N],而不是N!
    while(left<right)
    {
        int mid=(left+right)>>1;
        if(dp_check(mid)) right=mid;
        else left=mid+1;//mid也不符合条件,所以是mid+1
    }
    printf("%d",right);
}
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你好!感谢你的提问。根据你的要求,我将为你解答关于NOIP2017普及t3问题的内容。 在这道题目中,我们需要解决一个关于棋盘的问题。题目描述了一个n×m的棋盘,每个格子上有一个非负整数。开始时,小明站在左上角的格子上,他可以向右或向下移动一步,每次移动的代价为目标格子上的数值。我们需要找到一条从左上角到右下角的路径,使得移动的总代价最小。 解决这个问题的一个常见的方法是使用动态规划(Dynamic Programming)。我们可以创建一个二维数dp,其中dp[i][j]表示从起点到达坐标为(i, j)的格子时的最小代价。然后,我们可以按照从左上角到右下角的顺序依次计算dp数的值。 具体的计算方法如下: 1. 首先,我们可以初始化dp数的第一行和第一列,即dp[0][j]和dp[i][0],它们表示从起点到达第一行和第一列的格子时的最小代价。初始化的方法是累加前面的格子的代价。 2. 接下来,我们可以使用一个双重循环,从(1, 1)开始遍历整个棋盘。对于每个格子(i, j),我们可以选择从上方格子(i-1, j)或左方格子(i, j-1)中选择一个代价较小的路径,并加上当前格子的代价。即dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]。 3. 最后,当我们计算完dp数的所有值后,dp[n-1][m-1]即为从起点到达右下角的格子时的最小代价。 这样,我们就可以得到从左上角到右下角的最小代价。希望能对你的问题有所帮助!如果你还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值