[NOIP2017 普及组]跳房子 【题解】
题目背景#
NOIP2017 普及组 T4
题目描述#
跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一。
跳房子的游戏规则如下:
在地面上确定一个起点,然后在起点右侧画
玩家每次都必须跳到当前位置右侧的一个格子内。玩家可以在任意时刻结束游戏,获得的分数为曾经到达过的格子中的数字之和。
现在小 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 说明#
花费
输入输出样例 2 说明#
由于样例中
数据规模与约定#
本题共 10 组测试数据,每组数据等分。
对于全部的数据满足
对于第
对于第
对于第
浅浅地分析下?#
#
-
首先,如果是我去考试,这是第四题啊!这说明这什么,这题是提高组难度的!
-
于是乎,我会去骗分
-
我一看,那个
的应该是最好骗的吧,直接把所有正数加起来,如果小于 ,那就输出 咯 -
我的眼睛渐渐地移到了
上,贪婪的目光似要把这 分硬生生的拿chi下diao -
我的念头从正数移到了……暴搜上!
-
枚举所有可能的答案,在判断这个方案是否能达到
,是就输出啦 -
那个……怎么判断这个方案怎么达到
? -
爆枚呗!每个点最多有
个决策,一一枚举再记忆化一下应该可以拿下这10分吧?(我没试过)
#
-
刚刚我们在分析枚举所有可能的答案时,我们是
循环一个一个的枚举 -
有没有更优的枚举方法?
-
有!因为这一段格子是连续的,我们很容易想到了二分答案!
-
定义两个指针,
和 -
-
如果这个答案可以行(
函数),由于题目要求的是最小,所以答案可能在左边, ,否则答案在右边 -
这里为什么是
而不是 ? 因为我们输出的是 -
现在的重点是怎么写
函数? -
我们刚刚的思路是暴力枚举,再思考一下?
-
线性的?每个点最多有n个决策?达到
?这怎么看都想动态规划啊 -
于是我们来设计状态,
表示跳到了第 个格子所得到的最大分数
-
这里,就有同学有疑问了(
应该只有我)有没有可能往前跳?答案是不可能的 -
想想,如果有三个点从前到后分别是
号,往前跳指的是从 跳到 ,然后跳到 ,那为何不直接一次跳过去呢?所以只需要往后转移 -
时间复杂度为
-
这样就可以过
的点了#include<bits/stdc++.h> using namespace std; #define INF 1e9 struct node{ int pos=0,value=0; }a[500005]; int n,d,k; int dp[500005]; bool check(int minn,int maxx){ dp[0]=0LL; for(int i=1;i<=n;i++){ dp[i]=-INF; for(int j=0;j<i;j++) if(a[i].pos-a[j].pos>=minn&&a[i].pos-a[j].pos<=maxx) dp[i]=max(dp[i],dp[j]+a[i].value); if(dp[i]>=k) return true; } return false; } int main() { cin>>n>>d>>k; for(int i=1;i<=n;i++) cin>>a[i].pos>>a[i].value; int l=0,r=INF; while(l<r){ int mid=(l+r)>>1; if(check(max(1,d-mid),d+mid)) r=mid; else l=mid+1; } cout<<(r==INF?-1:r); return 0; }
#
-
我们考虑在50分的做法上继续优化
-
我们看这个复杂度
,最大的麻烦是什么? -
那肯定是
啊,有没有办法把 优化成 或 呢? -
我们观察
函数,发现 的转移都是在它之前满足条件的,动态变化的,很自然的想到了单调队列 -
所以我们只需要维护
就行了 -
怎么维护呢?
-
定义一个指针
,让他指向第一个可行的点
while(a[i].pos-a[now].pos>maxx&&now<i) now++;
- 然后扫一遍可行的区间
while(a[i].pos-a[now].pos<=maxx&&a[i].pos-a[now].pos>=minn&&now<i){
- 更新一下这个点,讲不可行的点从队尾弹出,不可行包括比
小或者超出了区间
while(!q.empty()&&(dp[q.back()]<=dp[now]||a[i].pos-a[q.back()].pos>maxx)) q.pop_back();
- 将新的点从队尾
,同时,指针枚举下一个点
q.push_back(now),now++;
- 最后在
外,将队首不可行的方案弹出
while(!q.empty()&&a[i].pos-a[q.front()].pos>maxx) q.pop_front();
- 最后判断和转移
if(!q.empty()) dp[i]=dp[q.front()]+a[i].value;
if(dp[i]>=k) return true;
-
注意,
千万不要重新赋值,这样才能保证每个点进出一遍 -
时间复杂度为
-
记得开
哦,然后你就可以 啦
#include<bits/stdc++.h>
using namespace std;
#define INF 1e18
typedef long long ll;
struct node{
ll pos=0,value=0;
}a[500005];
ll n,d,k;
ll dp[500005];
bool check(ll minn,ll maxx){
for(int i=1;i<=n;i++) dp[i]=-INF;
dp[0]=0LL;
deque<int> q;
int now=0;
for(ll i=1;i<=n;i++){
while(a[i].pos-a[now].pos>maxx&&now<i) now++;
while(a[i].pos-a[now].pos<=maxx&&a[i].pos-a[now].pos>=minn&&now<i){
while(!q.empty()&&(dp[q.back()]<=dp[now]||a[i].pos-a[q.back()].pos>maxx)) q.pop_back();
q.push_back(now);
now++;
}
while(!q.empty()&&a[i].pos-a[q.front()].pos>maxx) q.pop_front();
if(!q.empty()) dp[i]=dp[q.front()]+a[i].value;
if(dp[i]>=k) return true;
}
return false;
}
int main()
{
cin>>n>>d>>k;
for(ll i=1;i<=n;i++) cin>>a[i].pos>>a[i].value;
ll l=1,r=a[n].pos;
while(l<r){
ll mid=(l+r)>>1;
if(check(max((long long)1,d-mid),d+mid)) r=mid;
else l=mid+1;
}
cout<<(r==INF?-1:r);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!