单调队列 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;
}