JZOJ 1407. 教主的游乐场

Description

  Orz教主的成员为教主建了一个游乐场,在教主的规划下,游乐场有一排n个弹性无敌的跳跃装置,它们都朝着一个方向,对着一个巨大的湖,当人踩上去装置可以带你去这个方向无限远的地方,享受飞行的乐趣。但是等这批装置投入使用时,却发现来玩的人们更喜欢在这些装置上跳来跳去,并且由于这些装置弹性的优势,不但它们能让人向所对的方向能跳很远,也都能向相反方向跳一定的距离。
  于是教主想出了个游戏,这n个装置按朝向相反的方向顺序以1..n编号。第i个装置可以跳到1..i-1个装置,且每个装置有一个不一定相同的反方向弹性a[i],代表第i个装置还可以跳到第i+1..i+a[i]个装置。教主指定一个起始的装置,问从这个装置开始,最少需要连续踩几次装置(起始的装置也算在内),可以跳到第n个装置的后方,即若第k个装置有k+a[i]>n,那么从第k个装置就可以跳到第n个装置的后方。
  (PS:你可以认为有n+1个装置,即需要求多少次能条到第n+1个装置)

Input

  输入的第1行包含两个正整数n,m,为装置的数目以及询问的次数。
  第2行包含n个正整数,第i个正整数为a[i],即第i个装置向反方向最大跳跃的长度。
  第3行包含了m个正整数,为询问从哪一个装置开始,最少要几次跳到第n个的后方。
  数字之间用空格隔开。

Output

  输出包含1行,这一行有m个正整数,对于每一个询问,输出最少需要踩的装置数,数字之间用空格隔开。
  行末换行且没有多余的空格。

Sample Input

5 5
2 4 1 1 1
1 2 3 4 5

Sample Output

2 1 2 2 1

Data Constraint

Hint

【样例说明】
  若从第1个装置开始则跳到第2个装置,接着就可以跳到第n个装置的后方。
  若从第3个装置开始则同样跳到第2个装置。
  若从第4个装置开始可以跳到第2个装置或最后一个装置,接着跳出第n个装置,答案同样为2。

【数据规模】
  对于20%的数据,有n≤10;
  对于40%的数据,有n≤100,m≤10;
  对于60%的数据,有n≤1000,a[i]≤1000,m≤500;
  对于100%的数据,有n≤100000,a[i]≤n,m≤50000。

分析

无论从哪个装置开始,若要跳跃次数最少,最多向左跳1次,并且这1次是在一开始跳的,因为如果向右跳后再向左跳,那么显然这次向左能跳到的装置前面也可以跳到。那么每次跳跃则要选择一个能让下一步跳到的位置编号尽量大的装置,因为跳到第i个装置,它下一次的行动范围为[1, i+a[i]],所以显然要i+a[i]得到最大。这样可以得到60%的分数。
正因为每次选择一个能让下一步跳到的位置编号尽量大的装置总可以得到最优解,对于每个装置,下一步能跳到的装置编号为i+a[i](称之为右边界),那么若按右边界排序从大到小排序后,每个装置为起点的答案就会是不下降的。若在k步能到达的装置里面选一个装置编号最小的i,那么对于k步内不可达的装置,若右边界大等于i,那么它即为k+1步可达的,同样在里面选出个编号最小的。由于可以向左无限跳,所以不必考虑左边界。1步可达的编号为n+1。
PS:抽象模型,每个装置变成区间[i, i+a[i]],问题就变成了区间覆盖的变种。

代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <complex>
#include <cstdio>

#define N 100010
#define INF 0x7fffffff
#define sqr(x) ((x) * (x))
#define pi acos(-1)

int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9'){if (ch == '-') f = -1; ch = getchar();}
    while (ch >= '0' && ch <= '9')  {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}

int f[N];
int a[N];

int main()
{
    int n = read(), m = read();
    for (int i = 1; i <= n; i++)
        a[i] = read();
    int now = n + 1;
    f[now] = 0;
    while (now > 1)
    {
        int i;
        for (i = 1; i < now; i++)
            if (a[i] + i >= now)
                break;
        f[i] = f[now] + 1;
        for (int j = i + 1; j < now; j++)
            if (a[j] + j >= now)
                f[j] = f[now] + 1;
            else f[j] = f[i] + 1;
        now = i;
    }
    int Q;
    for (int i = 1; i <= m; i++)
    {
        Q = read();
        printf("%d ", f[Q]);
    }
    printf("\n");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值