Phrvth量子力学对不起,爱因斯坦

[NOIP2017 普及组]跳房子 【题解】

Hi,Phrvth·2023-01-12 16:12·540 次阅读

[NOIP2017 普及组]跳房子 【题解】

题目背景#

NOIP2017 普及组 T4

题目描述#

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

跳房子的游戏规则如下:

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

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

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

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

输入格式#

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

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

输出格式#

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

样例 #1#

样例输入 #1#

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

样例输出 #1#

Copy
2

样例 #2#

样例输入 #2#

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

样例输出 #2#

Copy
-1

提示#

输入输出样例 1 说明#

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

输入输出样例 2 说明#

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

数据规模与约定#

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

对于全部的数据满足1n5×1051d2×1031xi,k109|si|<105

对于第 1,2 组测试数据,保证 n10

对于第 3,4,5 组测试数据,保证 n500

对于第 6,7,8 组测试数据,保证 d=1

浅浅地分析下?#

n10#

  • 首先,如果是我去考试,这是第四题啊!这说明这什么,这题是提高组难度的!

  • 于是乎,我会去骗分

  • 我一看,那个1的应该是最好骗的吧,直接把所有正数加起来,如果小于k,那就输出1

  • 我的眼睛渐渐地移到了n10上,贪婪的目光似要把这10分硬生生的拿chidiao

  • 我的念头从正数移到了……暴搜上!

  • 枚举所有可能的答案,在判断这个方案是否能达到k,是就输出啦

  • 那个……怎么判断这个方案怎么达到k?

  • 爆枚呗!每个点最多有n个决策,一一枚举再记忆化一下应该可以拿下这10分吧?(我没试过)

n500#

  • 刚刚我们在分析枚举所有可能的答案时,我们是for循环一个一个的枚举

  • 有没有更优的枚举方法?

  • 有!因为这一段格子是连续的,我们很容易想到了二分答案

  • 定义两个指针,lr

  • mid=(l+r)/2=(l+r)>>1

  • 如果这个答案可以行(check()函数),由于题目要求的是最小,所以答案可能在左边,r=mid,否则答案在右边l=mid+1

  • 这里为什么是r=mid而不是r=mid1? 因为我们输出的是r

  • 现在的重点是怎么写check()函数?

  • 我们刚刚的思路是暴力枚举,再思考一下?

  • 线性的?每个点最多有n个决策?达到k?这怎么看都想动态规划啊

  • 于是我们来设计状态,dpi表示跳到了第i个格子所得到的最大分数

dpi=max(dpj+a[i].value,dpi)(minna[i].posa[j].posmaxx)

  • 这里,就有同学有疑问了(应该只有我)有没有可能往前跳?答案是不可能的

  • 想想,如果有三个点从前到后分别是1,2,3号,往前跳指的是从1跳到3,然后跳到2,那为何不直接一次跳过去呢?所以只需要往后转移

  • 时间复杂度为O(n2log2MAX(xi))

  • 这样就可以过n500的点了

    Code

    Copy
    #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; }

n5105#

  • 我们考虑在50分的做法上继续优化

  • 我们看这个复杂度O(n2log2MAX(xi)),最大的麻烦是什么?

  • 那肯定是n2啊,有没有办法把n2优化成nlog2nn呢?

  • 我们观察check()函数,发现dp的转移都是在它之前满足条件的,动态变化的,很自然的想到了单调队列

  • 所以我们只需要维护deque就行了

  • 怎么维护呢?

  • 定义一个指针now,让他指向第一个可行的点

Copy
while(a[i].pos-a[now].pos>maxx&&now<i) now++;
  • 然后扫一遍可行的区间
Copy
while(a[i].pos-a[now].pos<=maxx&&a[i].pos-a[now].pos>=minn&&now<i){
  • 更新一下这个点,讲不可行的点从队尾弹出,不可行包括比dpnow小或者超出了区间
Copy
while(!q.empty()&&(dp[q.back()]<=dp[now]||a[i].pos-a[q.back()].pos>maxx)) q.pop_back();
  • 将新的点从队尾push,同时,指针枚举下一个点
Copy
q.push_back(now),now++;
  • 最后在while外,将队首不可行的方案弹出
Copy
while(!q.empty()&&a[i].pos-a[q.front()].pos>maxx) q.pop_front();
  • 最后判断和转移
Copy
if(!q.empty()) dp[i]=dp[q.front()]+a[i].value; if(dp[i]>=k) return true;
  • 注意,now千万不要重新赋值,这样才能保证每个点进出一遍

  • 时间复杂度为O(nlog2MAX(xi))

  • 记得开longlong哦,然后你就可以AC

Code

Copy
#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; }
posted @   Phrvth  阅读(540)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
目录
?