P3957 [NOIP2017 普及组] 跳房子

[NOIP2017 普及组] 跳房子

题目背景

NOIP2017 普及组 T4

题目描述

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

跳房子的游戏规则如下:

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

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

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

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

输入格式

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

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

输出格式

共一行,一个整数,表示至少要花多少金币来改造他的机器人。若无论如何他都无法获得至少 k k k 分,输出 − 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 说明

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

输入输出样例 2 说明

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

数据规模与约定

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

对于全部的数据满足 1 ≤ n ≤ 5 × 1 0 5 1 \le n \le 5\times10^5 1≤n≤5×105, 1 ≤ d ≤ 2 × 1 0 3 1 \le d \le2\times10^3 1≤d≤2×103, 1 ≤ x i , k ≤ 1 0 9 1 \le x_i, k \le 10^9 1≤xi,k≤109, ∣ s i ∣ < 1 0 5 |s_i| < 10^5 ∣si∣<105。

对于第 1 , 2 1, 2 1,2 组测试数据,保证 n ≤ 10 n\le 10 n≤10。

对于第 3 , 4 , 5 3, 4, 5 3,4,5 组测试数据,保证 n ≤ 500 n \le 500 n≤500。

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

分步分析

二分?

我们可以看到,我们应输出g,假设 g 1 g_1 g1为正确答案,即 g = g 1 g=g_1 g=g1,可以取最大值,那么 g 1 + 1 g_1+1 g1+1也可以取最大值,故对g可以二分答案,检查是否有k分即可

检查?

我们很容易得到一下程序

cpp 复制代码
void solve(){
	int l=0,r=dis[n],ans=-1;
	while(l<=r){
		int mid=(l+r)>>1;
		if (check(mid)) ans=mid,r=mid-1;
		else l=mid+1;
	}
	cout<<ans;
}

关键是怎么检查答案?

我们设dp[i]是以第i个点结束的最大值,我们发现具有无后效性,可以dp,我们求出二分出的g给定的跳跃区间,进行dp即可

cpp 复制代码
bool check(int g){
	int l=0,r=0;
	int ming=max(1,d-g),maxg=d+g;
	dp[0]=0;
	for (int i=1;i<=n;i++){
		dp[i]=-1e6;
		while (r<=n and dis[i]-dis[r]>=ming) r++;
		while(l<=n and dis[i]-dis[l]>maxg) l++;
		for (int j=l;j<r;j++) dp[i]=max(dp[i],dp[j]);
		dp[i]+=score[i];
		if (dp[i]>=k) return true;
	}
	return false;
}

效率O( n 2 \text{n}^2 n2),不是很好

我们发现,由于所有的dp值均来自于[l,r),故可以进行单调队列优化。(见代码)

代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int M=1e6;
#define int long long
int dis[M],score[M],dp[M];
int n,d,k;
deque<int> q;
bool check(int g){
	q.clear();
	int ming=max(1ll,d-g),maxg=d+g;
	for (int i=0;i<=n;i++) dp[i]=-1e18;
	dp[0]=0;
	int las=0;	
	for(int i=1;i<=n;i++){
		int ld=dis[i]-maxg,rd=dis[i]-ming;
		
		while(dis[las]<ld) las++;
		while(ld<=dis[las] and dis[las]<=rd and las<i){
			while(!q.empty() and dp[q.back()]<=dp[las])
				q.pop_back();
			q.push_back(las++);
		}
	
		while(!q.empty() and(dis[q.front()]<ld)) q.pop_front();
		if (!q.empty()) dp[i]=dp[q.front()]+score[i];
		if (dp[i]>=k) return 1;
	}
	return false;
}
void read(){
	cin>>n>>d>>k;
	for (int i=1;i<=n;i++) cin>>dis[i]>>score[i];
}
void solve(){
	int l=0,r=dis[n],ans=-1;
	while(l<=r){
		int mid=(l+r)>>1;
		if (check(mid)) ans=mid,r=mid-1;
		else l=mid+1;
	}
	cout<<ans;
}
signed main(){
	read();
	solve();
	return 0;	
}

分析

只看check:

cpp 复制代码
bool check(int g){
	q.clear();
	int ming=max(1ll,d-g),maxg=d+g;
	for (int i=0;i<=n;i++) dp[i]=-1e18;
	dp[0]=0;
	int las=0;	
	for(int i=1;i<=n;i++){
		int ld=dis[i]-maxg,rd=dis[i]-ming;
		
		while(dis[las]<ld) las++;
		while(ld<=dis[las] and dis[las]<=rd and las<i){
			while(!q.empty() and dp[q.back()]<=dp[las])
				q.pop_back();
			q.push_back(las++);
		}
	
		while(!q.empty() and(dis[q.front()]<ld)) q.pop_front();
		if (!q.empty()) dp[i]=dp[q.front()]+score[i];
		if (dp[i]>=k) return 1;
	}
	return false;
}

ming与maxg是所能移动距离的区间[ming,maxg],而ld,rd是到达能到达i点的区间[ld,rd]

先选取第一个符合条件的点las,即 d i s [ l a s ] ∈ [ l d , r d ] dis[las]\in[ld,rd] dis[las]∈[ld,rd],后每个点都尝试加入,再选点时,需要出队不符合条件的元素

相关推荐
白榆maple10 分钟前
(蓝桥杯C/C++)——基础算法(下)
算法
JSU_曾是此间年少15 分钟前
数据结构——线性表与链表
数据结构·c++·算法
此生只爱蛋1 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
咕咕吖2 小时前
对称二叉树(力扣101)
算法·leetcode·职场和发展
九圣残炎2 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
lulu_gh_yu2 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
丫头,冲鸭!!!3 小时前
B树(B-Tree)和B+树(B+ Tree)
笔记·算法
Re.不晚3 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
为什么这亚子4 小时前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
4 小时前
开源竞争-数据驱动成长-11/05-大专生的思考
人工智能·笔记·学习·算法·机器学习