P3957 [NOIP 2017 普及组] 跳房子
P3957 [NOIP 2017 普及组] 跳房子
题目背景
NOIP2017 普及组 T4
题目描述
跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一。
跳房子的游戏规则如下:
在地面上确定一个起点,然后在起点右侧画 nnn 个格子,这些格子都在同一条直线上。每个格子内有一个数字(整数),表示到达这个 格子能得到的分数。玩家第一次从起点开始向右跳,跳到起点右侧的一个格子内。第二次再从当前位置继续向右跳,依此类推。规则规定:
玩家每次都必须跳到当前位置右侧的一个格子内。玩家可以在任意时刻结束游戏,获得的分数为曾经到达过的格子中的数字之和。
现在小 R 研发了一款弹跳机器人来参加这个游戏。但是这个机器人有一个非常严重的缺陷,它每次向右弹跳的距离只能为固定的 ddd。小 R 希望改进他的机器人,如果他花 ggg 个金币改进他的机器人,那么他的机器人灵活性就能增加 ggg,但是需要注意的是,每 次弹跳的距离至少为 111。具体而言,当 g<dg<dg<d 时,他的机器人每次可以选择向右弹跳的距离为 d−g,d−g+1,d−g+2,…,d+g−1,d+gd-g,d-g+1,d-g+2,\ldots,d+g-1,d+gd−g,d−g+1,d−g+2,…,d+g−1,d+g;否则当 g≥dg \geq dg≥d 时,他的机器人每次可以选择向右弹跳的距离为 1,2,3,…,d+g−1,d+g1,2,3,\ldots,d+g-1,d+g1,2,3,…,d+g−1,d+g。
现在小 R 希望获得至少 kkk 分,请问他至少要花多少金币来改造他的机器人。
输入格式
第一行三个正整数 n,d,kn,d,kn,d,k,分别表示格子的数目,改进前机器人弹跳的固定距离,以及希望至少获得的分数。相邻两个数 之间用一个空格隔开。
接下来 nnn 行,每行两个整数 xi,six_i,s_ixi,si,分别表示起点到第 iii 个格子的距离以及第 iii 个格子的分数。两个数之间用一个空格隔开。保证 xix_ixi 按递增顺序输入。
输出格式
共一行,一个整数,表示至少要花多少金币来改造他的机器人。若无论如何他都无法获得至少 kkk 分,输出 −1-1−1。
输入输出样例 #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 说明
花费 222 个金币改进后,小 R 的机器人依次选择的向右弹跳的距离分别为 $ 2, 3, 5, 3, 4,3$,先后到达的位置分别为 2,5,10,13,17,202, 5, 10, 13, 17, 202,5,10,13,17,20,对应 $ 1, 2, 3, 5, 6, 7$ 这 666 个格子。这些格子中的数字之和 $ 15$ 即为小 R 获得的分数。
样例 2 说明
由于样例中 777 个格子组合的最大可能数字之和只有 181818,所以无论如何都无法获得 202020 分。
数据规模与约定
本题共 10 组测试数据,每组数据等分。
对于全部的数据满足 1≤n≤5×1051 \le n \le 5\times10^51≤n≤5×105,1≤d≤2×1031 \le d \le2\times10^31≤d≤2×103,1≤xi,k≤1091 \le x_i, k \le 10^91≤xi,k≤109,∣si∣<105|s_i| < 10^5∣si∣<105。
对于第 1,21, 21,2 组测试数据,保证 n≤10n\le 10n≤10。
对于第 3,4,53, 4, 53,4,5 组测试数据,保证 n≤500n \le 500n≤500。
对于第 6,7,86, 7, 86,7,8 组测试数据,保证 d=1d = 1d=1。
思路
1.n格子的数目
2.d一次弹跳的距离
3.k是至少要获得的分数(多了当然也可以,不能少),就是跳到格子的数字的和
4.只能往右跳
5.问题:g金币改动弹跳的范围,跳动距离选择面变广,max(d-g,1)<=x<=d+g
6.金币够,能跳任意距离,就能达更多的格子,得到更多的格子和
这单调递增,有序,可以二分找O(logs)
7.有了金币数,跳动范围,就可以算到达每个点的格子和,
f[i]=f[j]+v[i]
双循环就能动态规划算出最大格子和O(n^2),超时
8. 单调队列只留下前序最大格子和,可以O(n)
f[i]=q[l]+v[i]
单调队列降序留存能达i的前序格子和
代码
#include <bits/stdc++.h>
using namespace std;
const int N=5e5+5;//格子数量上限
const long long inf=1e18; //表示"不可达"的极限值
struct node{//格子的结构体,没有构造函数,叫聚合类型 int w,//格子距离起点的位置 v;//格子的分数
}ge[N];//存储所有格子的数组
int n,//格子总数 d,//机器人初始的固定跳跃距离 k;//目标分数(至少要获得的分数)
long long f[N],//动态规划状态,到达第i个格子时能获得的最大分数 q[N],//单调队列,保持降序,用来找到最大值(到i点的最大分数,源自f[i])。 qw[N],//单调队列,i元素对应的位置,用来判断(单调队列的区间)qw[x]+mind<=i<=qw[x]+maxdhe;//所有格子的最大和(用于判断无解)
bool check(int g){//用来判断,当跳动距离的改动范围是g时,能跳到的格子的和是否>=k int mind=max(1,d-g),maxd=d+g;//mind不是变动范围,而是实际跳跃数,不能是0,否则原地 //机器人从 x 跳到 i,跳跃距离是「i 的位置 - x 的位置」,这个距离必须在 [mind, maxd] 范围内 int l=0,r=-1,//单调队列的首尾指针 x=0;//i的前序,要达i(用来入队,只遍历一次) f[0]=0;//0起点设了就可以 //memset(f,0xcf,sizeof(f));//除了0起点外所有格子都不可达,//memset(f,0,sizeof(f));这样也可以,除了0外其他无所谓,动态规划新得for(int i=1;i<=n;i++){//遍历所有格子,动态规划O(n^2)计算到达该格子的最大分数。//单调队列,能达i点的前序x从队尾入队,替代前面小的格子和,保持队列降序。队首总是最大值,意义在于不二过,O(n) for(;x<=i&&ge[x].w+mind<=ge[i].w;x++){//可达:x的位置+最小跳跃距离<=i的位置(<=x的位置+最大跳跃距离) if(f[x]<=-inf)continue;//-inf表示不可达,不能入队。若入队,再碰到inf的格子,数字上变成可达 while(l<=r&&q[r]<=f[x])r--;//清除小于x的队尾,维护单调队列降序q[++r]=f[x],qw[r]=ge[x].w; //入队。该队列中元素对下个i可能也能用上 } //单调队列清队首不达i的格子,现在用不上往后也用不上 while(l<=r&&qw[l]+maxd<ge[i].w)l++;//超出最大跳跃距离 if(l<=r)f[i]=q[l]+ge[i].v;//动态转移方程。队首q[l]就是能跳到i的最大格子和+i格子数。后面要入队。q是i前的f,f是q算出来的 else f[i]=-inf;//队列没有有效元素,无法跳到i if(f[i]>=k)return 1;}return 0;//做不到
}
int main(){//freopen("data.cpp","r",stdin);cin>>n>>d>>k;for(int i=1;i<=n;i++){//0是起点,第一个格子是1 cin>>ge[i].w >>ge[i].v;if(ge[i].v>0)he+=ge[i].v;//累加正分数,用于快速判断无解 }if(he<k){cout<<-1;return 0;}//无解,无法得到k//金币数、跳动距离改动范围越大能选择的格子越多,格子和就越大,正比关系,有序,可以二分 int l=0,r=ge[n].w,m;//查找能跳到的格子的和大于等于(至少)k的跳动距离的改动范围g的最小值,也就是最少金币数while(l<r){//最后重合的位置就是最少的值 m=(l+r)>>1;if(check(m))r=m;//m可行,m上不要 else l=m+1;//不可行,m以左不要//始终在范围内,只是范围缩小了 }cout<<l;//l和r都是满足条件的第一个首元素(至少) return 0;
}
反思
二分要熟悉
动态规划要熟悉
单调队列要熟悉
就这样还有好多细节要处理
真是蜀道难