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。
7 4 10 2 6 5 -3 10 3 11 -3 13 1 17 6 20 2 12345678 样例输出 #1
2 1
7 4 20 2 6 5 -3 10 3 11 -3 13 1 17 6 20 2 12345678 样例输出 #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 5times10^51≤n≤5×105,1≤d≤2×1031 le d le2times10^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≤10nle 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。
刚刚我们在分析枚举所有可能的答案时,我们是forforfor循环一个一个的枚举
有没有更优的枚举方法?
有!因为这一段格子是连续的,我们很容易想到了二分答案!
定义两个指针,lll和rrr
mid=(l+r)/2=(l+r)>>1 mid=(l+r)/2=(l+r)>>1 mid=(l+r)/2=(l+r)>>1
如果这个答案可以行(check()check()check()函数),由于题目要求的是最小,所以答案可能在左边,r=midr=midr=mid,否则答案在右边l=mid+1l=mid+1l=mid+1
这里为什么是r=midr=midr=mid而不是r=mid−1r=mid-1r=mid−1? 因为我们输出的是rrr
现在的重点是怎么写check()check()check()函数?
我们刚刚的思路是暴力枚举,再思考一下?
线性的?每个点最多有n个决策?达到kkk?这怎么看都想动态规划啊
于是我们来设计状态,dpidp_idpi表示跳到了第iii个格子所得到的最大分数
dpi=max(dpj+a[i].value,dpi)(minn≤a[i].pos−a[j].pos≤maxx) dp_i=max(dp_j+a[i].value,dp_i)(minn le a[i].pos-a[j].pos le maxx) dpi=max(dpj+a[i].value,dpi)(minn≤a[i].pos−a[j].pos≤maxx)
这里,就有同学有疑问了(应该只有我)有没有可能往前跳?答案是不可能的
想想,如果有三个点从前到后分别是1,2,31,2,31,2,3号,往前跳指的是从111跳到333,然后跳到222,那为何不直接一次跳过去呢?所以只需要往后转移
时间复杂度为O(n2log2MAX(xi))O(n^2log_2MAX(x_i))O(n2log2MAX(xi))
这样就可以过n≤500n le 500n≤500的点了
CodeCodeCode
#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; }
c++
运行
123456789101112131415161718192021222324252627282930313233 n≤5∗105n le 5*10^5n≤5∗105 我们考虑在50分的做法上继续优化我们看这个复杂度O(n2log2MAX(xi))O(n^2log_2MAX(x_i))O(n2log2MAX(xi)),最大的麻烦是什么?那肯定是n2n^2n2啊,有没有办法把n2n^2n2优化成nlog2nnlog_2nnlog2n或nnn呢?我们观察check()check()check()函数,发现dpdpdp的转移都是在它之前满足条件的,动态变化的,很自然的想到了单调队列所以我们只需要维护dequedequedeque就行了怎么维护呢?定义一个指针nownownow,让他指向第一个可行的点while(a[i].pos-a[now].pos>maxx&&now<i) now++;
c++
运行
1 然后扫一遍可行的区间while(a[i].pos-a[now].pos<=maxx&&a[i].pos-a[now].pos>=minn&&now<i){
c++
运行
1 更新一下这个点,讲不可行的点从队尾弹出,不可行包括比dpnowdp_{now}dpnow小或者超出了区间while(!q.empty()&&(dp[q.back()]<=dp[now]||a[i].pos-a[q.back()].pos>maxx)) q.pop_back();
c++
运行
1 将新的点从队尾pushpushpush,同时,指针枚举下一个点q.push_back(now),now++;
c++
运行
1 最后在whilewhilewhile外,将队首不可行的方案弹出while(!q.empty()&&a[i].pos-a[q.front()].pos>maxx) q.pop_front();
c++
运行
1 最后判断和转移if(!q.empty()) dp[i]=dp[q.front()]+a[i].value; if(dp[i]>=k) return true;
c++
运行
12 注意,nownownow千万不要重新赋值,这样才能保证每个点进出一遍时间复杂度为O(nlog2MAX(xi))O(nlog_2MAX(x_i))O(nlog2MAX(xi))记得开longlonglongquad longlonglong哦,然后你就可以ACACAC啦CodeCodeCode
#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; }
c++
运行
1234567891011121314151617181920212223242526272829303132333435363738394041相关知识
NOIP2017初赛阅读程序写结果第4题题解
上海市计算机学会竞赛平台丙组比赛目录及题解持续更新中
[NOIP2012 普及组] 摆花(含代码)
追随兴趣,静待花开——《民间游戏之跳房子、翻花绳课程案例分析》
ZUST 程序设计算法竞赛基础【1】题解报告
洛谷 P3957 跳房子
[NOIP2012 普及组] 摆花
[NOIP2009 普及组] 细胞分裂
动态规划 摆花 题解
P1077 [NOIP2012 普及组] 摆花
网址: [NOIP2017 普及组]跳房子 【题解】 https://m.huajiangbk.com/newsview2534678.html
| 上一篇: 19家平台已上线 DeepSee |
下一篇: Python, C ++开发民间 |