【动态规划·真题】单调队列优化DP:跳房子

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Ronaldo7_ZYB/article/details/88040404

Problem

题目描述

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

在地面上确定一个起点,然后在起点右侧画 n 个格子,这些格子都在同一条直线上。每个格子内有一个数字(整数),表示到达这个格子能得到的分数。玩家第一次从起点开始向右跳,跳到起点右侧的一个格子内。第二次再从当前位置继续向右跳,依此类推。规则规定: 玩家每次都必须跳到当前位置右侧的一个格子内。玩家可以在任意时刻结束游戏,获得的分 数为曾经到达过的格子中的数字之和。

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

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

输入格式

输入文件名为 jump.in

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

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

输出格式

输出文件名为 jump.out。

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

题解

这道题位置 g g ,我们可以知道 g g 越大,越容易满足条件,因此答案具有单调性、采用二分答案。

我们二分枚举 g g ,则可以每次跳的区间长度是 m a x ( 1 , d g )   d + j max(1,d-g)~d+j

f [ i ] f[i] 表示跳到位置i的最大分数,则状态转移方程式: f [ i ] = m a x ( f [ j ] ) + s [ i ] f[i]=max(f[j])+s[i]

此时满足 m a x ( d g , 1 ) x i x j d + g max(d-g,1)≤x_i-x_j≤d+g ,即 x i d g x j x i m a x ( d g , 1 ) x_i-d-g≤x_j≤x_i-max(d-g,1) 。此时随着i的增加,符合取值的决策集合的范围也在不断递增。因此我们使用单调队列来不断地加入和排除每一个答案。

在每一次做的过程中:

  • 为了确保弹出的决策一定不可能成为接下来转移的最优决策,必须先把不符合左端点的弹出。因为如果小于左端点了,因为决策集合的递增,一定不可能成为接下来的最优决策。而加入的条件则是满足右端点的取值。
  • 先加入,后排出;因为加入的也可能是非法决策。
  • f 0 f_0 也要在枚举时加入而不能起先就加入,因为 0 0 和节点也不一定满足取值范围。

特别地,对于这道题:

  • 因为权值是负数,初始化需要正无穷。
  • 只要任意一个 f f 值大于 k k 即可。
  • 常见坑点 l o n g l o n g longlong

代码如下:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
LL n,d,k,h,t,j;
LL f[1000000];
LL q[1000000];
LL x[1000000];
LL s[1000000];
void push(LL x)
{
	while (h <= t && f[x] > f[q[t]]) t --;
	q[++ t]=x;
}
bool check(LL g)
{
	h=1,t=0,j=0;
	for (LL i=1;i<=n;++i) f[i]=-INT_MAX;
	for (LL i=1;i<=n;++i)
	{
		while (x[i]-x[j]>=max(d-g,1LL) && j<i) push(j++);
		while (h <= t && x[i]-x[q[h]]>d+g) h ++;
		if (h <= t && f[q[h]] != -INT_MAX) f[i]=f[q[h]]+s[i]; 
		if (f[i] >= k) return 1;
	}
	return 0;
}
int main(void)
{
	freopen("jump.in","r",stdin);
	freopen("jump.out","w",stdout);
	scanf("%lld %lld %lld",&n,&d,&k);
	for (LL i=1;i<=n;++i)
	    scanf("%lld %lld",x+i,s+i);
	LL l=0,r=1e9;
	while (l+1<r)
	{
		LL mid=l+r>>1; 
		if (check(mid)) r=mid;
		else l=mid;
	}
	if (check(l)) printf("%lld\n",l);
	else printf("%lld\n",r);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Ronaldo7_ZYB/article/details/88040404