(蓝桥真题)青蛙过河(思维+双指针/二分)

文章讨论了一个关于青蛙跳跃的问题,其中青蛙的跳跃能力为c,目标是在不超过2*x次跳跃内覆盖所有石头。关键在于确保每个长度为c的区间内的石头总高度至少为2*x。文章通过必要性和充分性证明了这一条件,并提供了两种解法:双指针寻找最小区间和二分查找判断可行性,分别具有O(n)和O(nlogn)的时间复杂度。

样例输入: 

5 1
1 0 1 0

样例输出:

4

分析:首先去和回是没有区别的,所以我们直接考虑是去2*x次即可

结论:假如说跳跃能力是c,那么对于任何一个长度为c的区间中的石头总高度都不能小于2*x。且当所有区间中的石头高度都大于等于2*x时,一定可以通过2*x次

证明:

先证明必要性:

如果要是存在某个长度为c的区间的石头总高度小于2*x,那么也就是说落到这个区间上石头的次数是小于2*x的,那么也就是必然会有几次是不经过这个区间的,但是由于这个区间长度为c,想要不经过这个区间直接跳过去那么弹跳能力显然要大于c,所以这是不可能的

下面证明充分性:

我们假设2*x只青蛙同时跳到区间1~c上的石头,那么现在我们考虑使1位置上的青蛙跳到第c+1个位置,如果要是h[1]<=h[c+1],那么这个显然是可以实现的,但是如果要是h[1]>h[c+1],那么我们需要分析一下,因为h[2]+h[3]+……+h[c+1]>=2*x,则h[2]+h[3]+……+h[c]>=2*x-h[c+1],那么我们就有h[1]+h[2]+h[3]+……+h[c]>=2*x+h[1]-h[c+1],我们可以把h[1]中的h[1]-h[c+1]调整至2~c块石头上,这样第一块石头上的青蛙个数就不会大于第c+1块石头上的青蛙,那么这样我们就能使得青蛙转移到2~c+1上,同理我们可以完成3~c+2上的转换……,这样我们就证明了结论的充分性

代码实现的话可以用双指针直接找出来满足所有区间和大于等于2*x的最小区间长度,复杂度是O(n),这个也是本博客使用的方法

还有一种方法就是直接二分长度然后判断是否可行,实现比较简单,复杂度是O(nlogn)

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#include<queue>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e5+10;
long long h[N];
int main()
{
	long long n,x;
	cin>>n>>x;
	x<<=1;
	for(int i=1;i<n;i++)
		scanf("%lld",&h[i]);
	long long ans=0,s=0;
	for(int i=0,j=0;j<=n;)
	{
		while(j<=n&&s<x)
			s+=h[j++];
		ans=max(ans,min(1ll*j,n)-i);
		s-=h[i++];
	}
	printf("%lld",ans);
	return 0;
}

蓝桥OJ青蛙过河问题描述为,小青蛙住在河边,要到河对岸学校学习,打算通过河里排成直线的石头跳到对岸,每次跳跃必须落在石头或岸上,每块石头有高度,从石头起跳高度会下降1,高度降为0后不能再跳上该石头。输入第一行包含河的宽度`n`和小青蛙去学校的天数`x`,实际过河次数为`2x`;第二行包含`n - 1`个非负整数,表示河中与小青蛙家相距不同位置石头的高度,`Hi > 0`表示有高度为`Hi`的石头,`Hi = 0`表示无石头。 解题思路主要是二分查找结合前缀和优化: 1. 二分查找:跳跃距离的范围是`[0, N]`,在这个范围内二分查找满足条件的最小跳跃距离。 2. 前缀和优化:通过前缀和数组`a`,可以快速计算任意区间内石头高度的总和。 3. 检查函数`check`:对于二分查找得到的中间跳跃距离`mid`,遍历每一个长度为`mid`的区间,计算区间内石头高度总和。如果有任何一个区间内的高度总和小于`2x`,则说明这个跳跃距离太小,需要增大;否则,这个跳跃距离是可行的,尝试减小。 以下是代码实现: ```cpp #include<iostream> #include<cstring> #include<algorithm> using namespace std ; const int N = 1e5 + 10 ; int n , x ; int a[N] ; int b[N] ; bool check(int mid){ bool flag = 1 ; for(int i = 0, j = mid; j < n ; j ++, i ++){ // 遍历每一个区间 int sum = a[j] - a[i]; if(sum >= 2*x) continue ; else flag = 0 ; // 如果有任何一个区间内的高度小于2*x 那这个跳跃距离就小了,要变大 } return flag ; } int main(){ cin >> n >> x ; for(int i = 1 ; i < n ; i ++){ cin >> a[i] ; a[i] += a[i-1]; // 这里借助前缀和来优化 } int l = 0 , r = N ; while(l < r){ // 二分 int mid = (l + r) >> 1 ; if(check(mid)) r = mid ; else l = mid + 1 ; } cout << l << endl ; return 0 ; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值