P3957 跳房子
题目描述
跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一。
跳房子的游戏规则如下:
在地面上确定一个起点,然后在起点右侧画 n 个格子,这些格子都在同一条直线上。每个格子内有一个数字(整数),表示到达这个 格子能得到的分数。玩家第一次从起点开始向右跳,跳到起点右侧的一个格子内。第二次再从当前位置继续向右跳,依此类推。规则规定:
玩家每次都必须跳到当前位置右侧的一个格子内。玩家可以在任意时刻结束游戏,获得的分数为曾经到达过的格子中的数字之和。
现在小 R研发了一款弹跳机器人来参加这个游戏。但是这个机器人有一个非常严重的缺陷,它每次向右弹跳的距离只能为固定的 dd。小 R希望改进他的机器人,如果他花 g个金币改进他的机器人,那么他的机器人灵活性就能增加 g ,但是需要注意的是,每 次弹跳的距离至少为 11 。具体而言,当 g<dg<d 时,他的机器人每次可以选择向右弹跳的距离为 d-g,d-g+1,d-g+2,…, d+g-2 , d+g-1 , d+g ;否则(当 g \geq dg≥d时),他的机器人每次可以选择向右弹跳的距离为 1 , 2 , 3 ,…, d+g-2 , d+g-1 , d+g。
现在小 R希望获得至少 k 分,请问他至少要花多少金币来改造他的机器人。
输入输出格式
输入格式:
第一行三个正整数 n , d , k ,分别表示格子的数目,改进前机器人弹跳的固定距离,以及希望至少获得的分数。相邻两个数之间用一个空格隔开。
接下来 n 行,每行两个正整数 xi,si ,分别表示起点到第 i 个格子的距离以及第 i 个格子的分数。两个数之间用一个空格隔开。保证 xi 按递增顺序输入。
输出格式:
共一行,一个整数,表示至少要花多少金币来改造他的机器人。若无论如何他都无法获得至少 k 分,输出 −1 。
输入输出样例
说明
【输入输出样例 1 说明】 2个金币改进后, 小 R 的机器人依次选择的向右弹跳的距离分别为2, 3, 5, 3, 4,3 先后到达的位置分别为 2, 5, 10, 13, 17, 20, 对应1, 2, 3, 5, 6, 7这6 个格子。这些格子中的数字之和15 即为小 R 获得的分数。
输入输出样例 2 说明
由于样例中 7个格子组合的最大可能数字之和只有 18 ,无论如何都无法获得20分
数据规模与约定
本题共 10 组测试数据,每组数据 10 分。
对于全部的数据满足1 ≤ n ≤ 500000, 1 ≤ d ≤2000, 1 ≤ x_i, k ≤ 10^9, |s_i| < 10^5。
对于第 1, 2组测试数据, n ≤ 10;
对于第3, 4, 5 组测试数据, n ≤ 500
对于第6, 7, 86,7,8 组测试数据, d = 1
解析:
(题外话,我能说这个题我好长时间理解错误么,我将这道题理解成了玩家可以从源点向右跳,跳到任何一个坐标为整数的位置,而本题要求:玩家每次都必须跳到当前位置右侧的一个格子内
,而这些格子是提前给定的。记录一下我的错误程序,可以把它改成另一个题呢
//起点为,玩家只可以想右跳,跳到坐标轴的整数点位置,只有个别位置有分数;
#include<iostream>
using namespace std;
int a[]={},b[];
int bo;
int l,h,t;
int n,d,k,maxx;
void dfs(int x,int sc,int dist,int t){//sc为到达x获得的分数,dist为当前灵活度
if (sc>=k) {
bo=;/*cout<<sc<<" :";for(int i=0;i<=t;i++)cout<<b[i]<<" ";cout<<endl;*/return ; }
for(int i=l;i<=h;i++){
int xx=x+i;
if (xx<=maxx){b[t+]=xx;dfs(xx,sc+a[xx],dist,t+);}
}
}
int main(){
int s=, x;
cin>>n>>d>>k;
for(int i=;i<=n;i++){ cin>>x;
cin>>a[x];
s+=a[x];
}
maxx=x;
if (s<k) cout<<-;
else
for(int i=;i<=;i++){//i为灵活性
bo=;
if(d>i)l=d-i;else l=;
h=d+i;
dfs(,,i,);
if (bo) {cout<<i;break;}
}
return ;
}
)
解法1:深度优先搜索(过3个点)
#include<cstring>
#include<iostream>
using namespace std;
const int maxn=;
int a[maxn][]={},b[maxn],f[maxn];
int bo;
int l,h,t,ans;
int n,d,k,maxx;
void dfs(int x,int sc,int pos){//sc为到达pos获得的分数
if (sc>=k) { bo=;return ; }//得到解
if(bo||x>n) return ;//越界或者已经得到解
if (a[x+][]-pos>h) return ;//不能跳那么远
if(a[x+][]-pos<l) {dfs(x+,sc,pos);return ;}//不能跳那么近
if (f[x+]<sc+a[x+][]){
f[x+]=sc+a[x+][];
dfs(x+,sc+a[x+][],a[x+][]);//走x+1这个点
dfs(x+,sc,pos);//跳过点x+1
}
else if(f[x+]<sc){ dfs(x+,sc,pos);return ;} } int main(){ int s=, x; cin>>n>>d>>k; for(int i=;i<=n;i++){ cin>>a[i][]>>a[i][]; if (a[i][]>)s+=a[i][]; } int lift=,right=a[n][],mid; ans=-;//变量定义的位置一定要准,ans不能放在下面的while循环里,否则会受最后结果的影响 if (s<k) cout<<-; else{ while (lift<=right){//等号必需存在,有时正解就是lift=right的时候 mid=(lift+right)>>; if(d>mid)l=d-mid;else l=; h=d+mid; memset(f,-,sizeof(f)); f[]=; bo=;//标志本次递归是否会有解,一定要放在while里噢! dfs(,,); if(!bo) lift=mid+; else {ans=mid;right=mid-;} } cout<<ans<<endl; } return ; }
解法2:二分+动态规划
二分很显然,花的钱越多跳到范围越广,更容易满足条件。
于是我们二分花多少钱,检验能否拿到k分。
f[i]表示跳到第i个格子的最高得分:f[i]=max(f[j])+a[i][2] (L<=a[i][1]-a[j][1]<=H)(如果直接使用动态规划时间复杂度是O(n2),能过5个点,其余超时)
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=;
int a[maxn][]={},b[maxn];
long long f[maxn];//a[i][1]是距离,a[i][2]是值。
int bo;
int l,h,t,ans;
int n,d,k,maxx,inf=-0X7f;
int dp(){
f[]=;//源点设置为0
for(int i=;i<=n;i++){
for(int j=;j<i;j++){
if(a[i][]-a[j][]>=l&&a[i][]-a[j][]<=h&&f[i]<f[j])//可以跳到
f[i]=f[j];
}
f[i]=f[i]+a[i][];
if(f[i]>=k) return ;
}
return ;
}
int main(){
int s=, x;
cin>>n>>d>>k;
for(int i=;i<=n;i++){
cin>>a[i][]>>a[i][];
if (a[i][]>)s+=a[i][];
}
int lift=,right,mid;
right=max(d,a[n][]);
ans=-;//变量定义的位置一定要准,ans不能放在下面的while循环里,否则会受最后结果的影响
if (s<k) cout<<-;
else{
while (lift<=right){//等号必需存在,有时正解就是lift=right的时候
mid=(lift+right)>>;
if(d>mid)l=d-mid;else l=;
h=d+mid;
memset(f,-,sizeof(f));
f[]=;
bo=;//标志本次递归是否会有解,一定要放在while里噢!
bo=dp();
// cout<<bo<<" "<<mid<<": ";
// for(int i=0;i<=n;i++)
// cout<<f[i]<<" ";
// cout<<endl;
if(!bo) lift=mid+;
else {ans=mid;right=mid-;}
}
cout<<ans<<endl;
// for(int i=0;i<n;i++)
// cout<<f[i]<<" ";
}
return ;
}
解法3:二分+动态规划+单调队列
这个题作为2017普及组的压轴题,各种坑。真正的考察大家的综合能力。
(1)数据类型的定义,求和的变量要定义为long long(因为:1≤xi,k≤109)
(2)二分时右边界right=max(d,a[n][1]);//比如d=100,a[n][1]=10 ,坑啊,要考虑全面,加上这一条就ac,否则只过了5个点呢!!!
(3)单调队列的变形使用
考虑到f[i]总是从前面可跳区间的最大值跳过来,随着i往后面走,这个区间也往后面走,如果采用单调队列,速度会快很多,因为每个元素只进队出队一次,时间复杂度为O(n)。
但是我们发现了一个问题,对于某个决策k,它可能不能更新i,但是它能更新j(j<i)。这样就不能直接用根据大小或者无法更新当前决策把k出队。
换句话说:不能像普通的单调队列优化一样仅凭这个决策的值小于另一个决策的值就将决策排除候选集合,因为我们的可行决策区间,是从[L,H],一个决策的值很大,但是他不一定可以更新他的下一个格子,一些较劣决策仍然可能成为最优决策,这是我们最需要注意的。
这里我们巧妙的对单调队列进行了变形:
入队:求f[i]前,先考虑j是否入队(j<i),如果j到i的最短距离<l,则j不入队,因为他对求i没有帮助,否则将所有符合条件的j按单调队列的规则(小于j的值出队)入队。然后求f[i].
出队:如果队首到i的距离大于H,则出队。
//考虑到f[i]总是从前面可跳区间的最大值跳过来,随着i往后面走,这个区间也往后面走,如果采用单调队列,速度会快很多,但是需要用到双端队列来构造单调队列,代码相对复杂一点,
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=500010;
int a[maxn][3]={0};//a[i][1]是距离,a[i][2]是值。
long long f[maxn],q[maxn][3];
int bo;
int l,h,t,ans=-1;
int n,d,k,maxx;
long long s;
const long long inf=-0X8080808080808080;
void init(){
cin>>n>>d>>k;
for(int i=1;i<=n;i++){
cin>>a[i][1]>>a[i][2];
if (a[i][2]>0)s+=a[i][2];//记录所有的正整数之和
}
}
int dp(){
memset(f,0x80,sizeof(f));
f[0]=0;//源点设置为0
memset(q,0,sizeof(q));
int head=1,tail=0,j=0;//tail指向队尾的最后一个元素,head指向队首元素
for(int i=1;i<=n;i++){ //求出i后并不入队,因为
while(a[i][1]-a[j][1]>=l&&j<i){ //巧:如果j到i的最短距离<l,则j不入队,一直到满足条件为止。 ,>h的在后面删除
if(f[j]!=inf){
while(head<=tail&&q[tail][2]<f[j]) tail--;//队尾指针比j小
tail++;//j入队
q[tail][1]=a[j][1];
q[tail][2]=f[j];
}
j++;
}
while (a[i][1]-q[head][1]>h&&tail>=head)head++;//i到队首的距离>h,则从队首出队。
if(head<=tail){
f[i]=q[head][2]+a[i][2];
if(f[i]>=k) return 1;
}
}
return 0;
}
int main(){
init();
int lift=0,right,mid;
right=max(d,a[n][1]);//比如d=100,a[n][1]=10 ,坑啊,要考虑全面,加上这一条就ac,否则只过了5个点呢!!!
ans=-1;//变量定义的位置一定要准,ans不能放在下面的while循环里,否则会受最后结果的影响
if (s<k) cout<<-1;
else{
while (lift<=right){//等号必需存在,有时正解就是lift=right的时候
mid=(lift+right)>>1;
if(d>mid)l=d-mid;else l=1;
h=d+mid;
memset(f,-127,sizeof(f));
f[0]=0;
bo=0;//标志本次递归是否会有解,一定要放在while里噢!
bo=dp();
if(!bo) lift=mid+1;
else {ans=mid;right=mid-1;}
}
cout<<ans<<endl;
}
return 0;
}
P3957 跳房子的更多相关文章
- Luogu P3957 跳房子
题面 跳房子,也叫跳飞机,是一种世界性儿童游戏,也是中国民间传统的体育游戏之一. 跳房子的游戏规则如下: 在地面上确定一个起点,然后在起点右侧画 n 个格子,这些格子都在同一条直线上.每个格子内有一 ...
- P3957 跳房子(二分答案+单调队列优化DP)
题目链接:https://www.luogu.org/contestnew/show/4468 题目大意:跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一. 跳房子的游戏规则 ...
- 洛谷 P3957 跳房子
https://www.luogu.org/problemnew/show/P3957 错误记录:1.没开longlong 2. -inf不够小 #include<cstdio> #inc ...
- 洛谷 P3957 跳房子 —— 二分答案+单调队列优化DP
题目:https://www.luogu.org/problemnew/show/P3957 先二分一个 g,然后判断: 由于转移的范围是一个区间,也就是滑动窗口,所以单调队列优化: 可以先令队尾为 ...
- 洛谷P3957 跳房子 题解 二分答案/DP/RMQ
题目链接:https://www.luogu.org/problem/P3957 这道题目我用到了如下算法: 线段树求区间最大值: 二分答案: DP求每一次枚举答案g时是否能够找到 \(\ge k\) ...
- 洛谷P3957 跳房子(Noip2017普及组 T4)
今天我们的考试就考到了这道题,在考场上就压根没有思路,我知道它是一道dp的题,但因为太弱还是写不出来. 下来评讲的时候知道了一些思路,是dp加上二分查找的方式,还能够用单调队列优化. 但看了网上的许多 ...
- 洛谷P3957 跳房子
普及组的题.....填坑来了. 当年的我一眼二分+DP,现在都佩服起自己来了...... 然后我们就写个二分,在check里面写单调队列优化DP即可. 然后就A了...... #include < ...
- 2018.09.26洛谷P3957 跳房子(二分+单调队列优化dp)
传送门 表示去年考普及组的时候失了智,现在看来并不是很难啊. 直接二分答案然后单调队列优化dp检验就行了. 注意入队和出队的条件. 代码: #include<bits/stdc++.h> ...
- 【单调队列】【P3957】 跳房子
传送门 Description 跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一. 跳房子的游戏规则如下: 在地面上确定一个起点,然后在起点右侧画 $n$ 个格子,这些格子都 ...
随机推荐
- SSH无法连接到服务器
SSH服务器会无法连接,有时候并不是密码的问题,可能由于你上次改了密码(就算改成跟上次一样也是一个效果)导致家目录下的known_hosts(/root/.ssh/known_hosts)不一样了并且 ...
- Reids 持久化AOF 重写实现原理
AOF重写 AOF重写并不需要对原有AOF文件进行任何的读取,写入,分析等操作,这个功能是通过读取服务器当前的数据库状态来实现的.(auto-aof-rewrite-percentage和auto-a ...
- PHP 如何获取客户端ip地址
PHP 如何获取客户端ip地址 一.总结 一句话总结:主要是使用$_SERVER的 REMOTE_ADDR 和 HTTP_X_FORWARDED_FOR 两个属性,在用户使用不同代理的时候这两个属性可 ...
- js转盘大抽奖 自定义概率
公司项目搞优惠活动,让做一个转盘抽奖的活动,转盘抽奖让他转起来 按照概率停止其实都麻烦,但是概率如果设置在前端就会很大的安全漏洞,所以无论为了安全性还是后期的维护问题都要把概率写到后台配置里然后读取配 ...
- Django web框架-----url path name详解
说明:mytestsite是django框架下的项目,quicktool是mytestsite项目中的应用 quicktool/view.py文件修改视图函数index(),渲染一个home.html ...
- Django_URL
视图函数介绍 视图一般都写在app的views中,并且视图的第一个参数永远都是request(HttpRequest)对象.这个对象存储了请求过来的所有信息,包括携带的参数以及一些头部信息等.再视图中 ...
- Linux和windows 平台下启动和关闭mysql服务
Linux平台下启动和关闭mysql服务 一.linux下查看mysql服务的两种方式: 方式一: [root@localhost bin]ps -ef|grep mysql 方式二: [root@l ...
- 使用Notepad++编译运行C/C++/Python程序
对我来说,比较常用的是C/C++/Python. 使用Notepad++编译运行单个源文件的C/C++/Python,比使用复杂的IDE更加快捷. 想要让Notepad++能够做到编译运行C/C++/ ...
- 访问链接出现 ERR_CONTENT_DECODING_FAILED 错误的解决办法
今天跑一个应用,别人的机器上都是好的,但是本地部署就是不行.访问页面调试工具console总是提示 ERR_CONTENT_DECODING_FAILED 错误. 就是数据表格无法显示,经排查,发现是 ...
- react中的传参方式
react是一个SPA模式,即组件嵌套租,在一个单页面的应用中组件间的数值传递是必不可少的,主要的传参方式大致有一下几种: 1,在挂载的地方给组件传参 ReactDOM.rander(<a na ...