单调队列 dp 二分查找 T4 P3957 [NOIP2017 普及组] 跳房子 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

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

跳房子的游戏规则如下:

在地面上确定一个起点,然后在起点右侧画 个格子,这些格子都在同一条直线上。每个格子内有一个数字(整数),表示到达这个 格子能得到的分数。玩家第一次从起点开始向右跳,跳到起点右侧的一个格子内。第二次再从当前位置继续向右跳,依此类推。规则规定:

玩家每次都必须跳到当前位置右侧的一个格子内。玩家可以在任意时刻结束游戏,获得的分数为曾经到达过的格子中的数字之和。

现在小 R 研发了一款弹跳机器人来参加这个游戏。但是这个机器人有一个非常严重的缺陷,它每次向右弹跳的距离只能为固定的 。小 R 希望改进他的机器人,如果他花 个金币改进他的机器人,那么他的机器人灵活性就能增加 ,但是需要注意的是,每次弹跳的距离至少为 。具体而言,当 时,他的机器人每次可以选择向右弹跳的距离为 ;否则当 时,他的机器人每次可以选择向右弹跳的距离为 。

现在小 R 希望获得至少 分,请问他至少要花多少金币来改造他的机器人。

输入格式

第一行三个正整数 ,分别表示格子的数目,改进前机器人弹跳的固定距离,以及希望至少获得的分数。相邻两个数 之间用一个空格隔开。

接下来 行,每行两个整数 ,分别表示起点到第 个格子的距离以及第 个格子的分数。两个数之间用一个空格隔开。保证 按递增顺序输入。

输出格式

共一行,一个整数,表示至少要花多少金币来改造他的机器人。若无论如何他都无法获得至少 分,输出 。

样例 #1

样例输入 #1

7 4 10
2 6
5 -3
10 3
11 -3
13 1
17 6
20 2

样例输出 #1

2

样例 #2

样例输入 #2

7 4 20
2 6
5 -3
10 3
11 -3
13 1
17 6
20 2

样例输出 #2

-1

提示

输入输出样例 1 说明

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

输入输出样例 2 说明

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

数据规模与约定

本题共 10 组测试数据,每组数据等分。

对于全部的数据满足,,,。

对于第 组测试数据,保证 。

对于第 组测试数据,保证 。

对于第 组测试数据,保证 。

思路

参考博客:题解 P3957 【跳房子】 - JLQ’s Blog - 洛谷博客 (luogu.com.cn)

给一些系列数, 求是否有一种选择方案, 至少获得 分。

定义 为以 结尾的所有跳跃路线获得的分数中最大值。

加上范围后, 可以从区间 中转移过来。 且当 金币数越大, 能跳的灵活度越高, 最后得到的分数不可能比之前更低, 故这里可以先二分 得到 值。

接着直接两层循环枚举求解就行。时间复杂度 。

但显然该题有 是没法过去的。

尝试优化第二层循环, 我们是在区间 中找一个最大的 来进行转移。且随着 的增加, 该区间也会整体右移, 也就是说, 可以用单调队列来滑动求区间最值。

建立单调队列 ,执行以下步骤:

  1. 对于每个阶段 (此题中每个状态的转移直接对应一个阶段),将决策候选集合中的元素  从队尾插入单调队列。此题中是区间最大值,所以队首应为未过时的所有决策中最大值。内部决策单调递减;
  2. 检查队首,排除过时决策;
  3. 直接取队首作为当前状态的最优决策,实行转移;
  4. 转移过后,判断分数是否已经大于 ,如果是,返回判定成功。

所有  的取值最多进队和出队一次,因此单次判定在线性复杂度实现。

最终时间复杂度为 完美AC。

代码

#include <bits/stdc++.h>
using namespace std;
 
typedef long long LL;
 
const int N = 5e5 + 10, INF = 0x3f3f3f3f;
LL x[N], s[N];
LL q[N], hh, tt;
LL f[N];
LL n, d, k;
 
bool check(LL mid)
{
    memset(f, -0x3f, sizeof f);
    LL lmin = max(d - mid, 1LL), rmax = d + mid;
    hh = 0, tt = -1;
    int j = 0;
    f[0] = 0;
    for (int i = 1; i <= n; i++)
    {
        while (x[i] - x[j] >= lmin && j < i)
        {
            if (f[j] > -INF)
            {
                while (hh <= tt && f[q[tt]] <= f[j])
                    tt--;
                q[++tt] = j;
            }
            j++;
        }
 
        while (hh <= tt && x[i] - x[q[hh]] > rmax)
            hh++;
 
        if (hh <= tt)
            f[i] = f[q[hh]] + s[i];
        if (f[i] >= k)
            return true;
    }
    return false;
}
 
void solve()
{
 
    cin >> n >> d >> k;
    for (int i = 1; i <= n; i++)
        cin >> x[i] >> s[i];
    
    LL l = -1, r = N;
    while (l != r - 1)
    {
        LL mid = l + r >> 1;
        if (check(mid))
            r = mid;
        else
            l = mid;
    }
    if (r == N)
        cout << "-1" << endl;
    else
        cout << r << endl;
}
 
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    solve();
    return 0;
}