洛谷P3957 跳房子(玩转单调队列)

传送门:跳房子

一、题目描述

洛谷P3957 跳房子(玩转单调队列)_第1张图片

洛谷P3957 跳房子(玩转单调队列)_第2张图片 洛谷P3957 跳房子(玩转单调队列)_第3张图片

二、解题思路

1)分析题目

 题意:机器人开始时在0的位置,总共有k个格子,每个格子的分数为gif.latex?S_%7Bi%7D,每个格子与原点的距离为gif.latex?x_%7Bi%7D

当花费g个金币后,机器人能跳的距离为gif.latex?%5Bmax%28d-g%2C1%29%2Cd+g%5D,也就是说机器人跳的最短距离不为0,最短为d-g1,要我们求花最少的金币g到达至少k分。

我们知道当g个金币能得到的分数,那g+1,g+2,······都能得到,因为当金币花费大时,能选的格子数就越多,得分可能就越多。

那我们怎样求这个最小金币的花费值g呢?

2)制定算法

显然我们可以用动态规划解题,第i个格子的最大分数有f[i]=max(f[j])+s[i],状态转移为第gif.latex?i个格子的最大得分等于第j个格子的最大得分加上第gif.latex?i个格子的得分。当然在限定距离下可能跳不到第gif.latex?i个格子,所以要注意判断条件。如果这样做,那我们的时间复杂度就为gif.latex?O%28n%5E%7B2%7D%29.。

因为这个可转移的状态是可以递推的,也就是说假设有一个状态先gif.latex?x无法转移到gif.latex?y,且状态gif.latex?zgif.latex?y后面,那gif.latex?x状态就一定不能转移到gif.latex?z状态。这里的状态指的是第几个点,也就是第几个位置。

那么根据这个性质我们就可以考虑用单调队列来优化。

那我们要维护的区间就是gif.latex?pos%5Bj%5D+max%281%2Cd-g%29%3Cpos%5Bi%5D%5Cleq%20pos%5Bj%5D+%28d+g%29

gif.latex?%5Bmax%28d-g%2C1%29%2Cd+g%5D  代表在第j个格子里能跳的距离。

其涵义为:在第j个格子能跳的最短距离和最大距离的之间的所有格子,让这些点加入到队列中,同时让此时的gif.latex?f%5Bi%5D更新为gif.latex?f%5Bfront%5Dgif.latex?+s%5Bi%5D,即如果能跳到这个格子,且我要跳到这个格子,我当然得从之前得分最大的格子跳过来,以此类推后面每个格子都是最大得分。同时我们也要维护队列的单调性,维护队首为最大值,并保证队首在区间内,如果不在区间,则表示队首已经不能跳到后面的格子需要将队首弹出。

代码:当花g个金币时能不能获得至少k分有:

bool check(int g){
	int pos=0,front=1,rear=0,l=max((long long)1,d-g),r=d+g;
	for(int i=1;i<=n;i++){
	/*如果当前位置加上可以跳的最短距离大于下一个点, 
	代表不可能跳到下一个点,反之则可能跳到下一个点  */ 
		while(x[pos]+l<=x[i]){	//当找到一个可能跳上去的点 
		//维护单调递减队列使得队首为最大值 
		while(front<=rear&&f[que[rear]]=k)return true;//当前的g能拿到至少要的分数 
	}
	return false; 		// 当前的g不能拿到至少要的分数 
}

3)过程演示:

当输入为如下是 ,即n=7,d=4,k=10    测试g=2的情况   能跳的距离为 gif.latex?%5B2%2C6%5D

7 4 10
2 6
5 -3
10 3
11 -3
13 1
17 6
20 2
gif.latex?x%5Bi%5D gif.latex?s%5Bi%5D 队列gif.latex?que gif.latex?i gif.latex?pos gif.latex?f%5Bi%5D
2 6 0 1 0 6
5 -3  6  2 1 6-3=3
10 3  3 3 2 3+3=6
11 -3 6  3  4 3 3+-(3)=0
13 1 6 3 1 5 4 6+1=7
17 6 7 6 5 7+6=13
20 2

 当然这个函数只能判断当金币为g时能不能得到至少k分,所以我们要找到这个g就要分治gif.latex?%5B1%2Cx%5Bn%5D%5D,如果花费g金币能获得k分,那么花更多金币就一定能获得至少k分,那我们就往小的方向找gif.latex?%5B0%2Cmid%5Dmid=(L+R)/2,否则如果花g金币不能得到这个分数,那我们必须花更多金币,也就往gif.latex?%5Bmid+1%2Cx%5Bn%5D%5D方向找,直到找到花费金币数量最小的g。

此时我们的时间花费为gif.latex?O%28nlogn%29

三、源代码

#include
using namespace std;
long long n,d,k,que[5000005],f[5000005],x[5000005],s[5000005];
bool check(int g){
	int pos=0,front=1,rear=0,l=max((long long)1,d-g),r=d+g;
	for(int i=1;i<=n;i++){
	/*如果当前位置加上可以跳的最短距离大于下一个点, 
	代表不可能跳到下一个点,反之则可能跳到下一个点  */ 
		while(x[pos]+l<=x[i]){	//当找到一个可能跳上去的点 
		//维护单调递减队列使得队首为最大值 
		while(front<=rear&&f[que[rear]]=k)return true;//当前的g能拿到至少要的分数 
	}
	return false; 		// 当前的g不能拿到至少要的分数 
}
int main(){
	 cin>>n>>d>>k;
	 for(int i=1;i<=n;i++)
	 cin>>x[i]>>s[i];
	 int L=1,R=x[n],mid,ans;
	 while(L

附上结果:

洛谷P3957 跳房子(玩转单调队列)_第4张图片

你可能感兴趣的:(算法,c++,开发语言)