第十三届蓝桥杯大赛软件赛省赛 C/C++ 大学 A 组 F题——青蛙过河

题目描述

小青蛙住在一条河边,它想到河对岸的学校去学习。小青蛙打算经过河里的石头跳到对岸。

河里的石头排成了一条直线,小青蛙每次跳跃必须落在一块石头或者岸上。不过,每块石头有一个高度,每次小青蛙从一块石头起跳,这块石头的高度就会下降 1,当石头的高度下降到 0 时小青蛙不能再跳到这块石头上(某次跳跃后使石头高度下降到 0 是允许的)。

小青蛙一共需要去学校上 x 天课,所以它需要往返 2x 次。当小青蛙具有一个跳跃能力 y 时,它能跳不超过 y 的距离。

请问小青蛙的跳跃能力至少是多少才能用这些石头上完 x 次课。

输入格式

输入的第一行包含两个整数 n, x,分别表示河的宽度和小青蛙需要去学校的天数。请注意 2x 才是实际过河的次数。

第二行包含 n − 1 个非负整数 H1, H2, · · · , Hn-1,其中 Hi > 0 表示在河中与小青蛙的家相距 i 的地方有一块高度为 Hi 的石头,Hi = 0 表示这个位置没有石头。

输出格式

输出一行,包含一个整数,表示小青蛙需要的最低跳跃能力。

结论:不存在高度之和小于2x的区间
当青蛙的最大跳跃步长为y时,我们假设存在某个长度为y的区间[l,r]
这2x次全程必定是需要经过这个区间2x次的。
假设存在某种走法可以不经过该区间,那么必定就有某一个的跳跃的步长超过r−l+1=y
这与题意矛盾。
这说明了若青蛙想要抵达对岸,就必须满足对于任意一个长度为y的区间,其高度之和都要大于等于2x

接下来只需要证明当每个区间和都大于等于2x
一定是成立的即可
由于每一个区间和都是大于等于2x的
因此对于任意一个点,当青蛙跳上来之后,都可以保证它的下一步有落脚点,这就说明了青蛙一定可以从起点跳至终点。
综上结论得证
因此check函数只需要判断是否每个区间的高度之和都是大于等于2x的就行了。

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;
int s[N];
typedef long long ll;
ll n, x;

//长度为m的区间是否和都不小于2x
bool check(int m) {
	for (int i = 1; i + m <= n; i++) {
		if (s[i + m - 1] - s[i - 1] < 2 * x)
			return false;
	}
	return true;
}

int main() {
	cin >> n >> x;
	//前缀和
	for (int i = 1; i < n; i++) {
		int a;
		cin >> a;
		s[i] = s[i - 1] + a;
	}
	//二分查找
	int l = 1, r = n;
	while (l < r) {
		int mid = (l + r) >> 1;
		if (check(mid))
			r = mid;
		else
			l = mid + 1;
	}
	cout << l << endl;
	return 0;
}

贪心+并查集

#include<iostream>
#include<algorithm>
#include<vector>

using namespace std;

const int N = 100010;
vector<int> h(N),s(N),p(N),a(N);
int n,x;

int find(int x){
    return p[x] == x ? p[x] : p[x]=find(p[x]);
}

int check(int y){
    //s是该块石头需要经过多少次,或者说是从该点出发的次数
    //初始化为0
    s.assign(s.size(), 0);
    //s[0]即左岸,需要从这里出发2*x(毕竟从一边出发2x次与来回x次一样)
    s[0] = 2 * x;
    //a也是石子高度
    for (int i = 1; i < n; i++)a[i] = h[i];
    //并查集初始化,父节点置为自己
    for (int i = 0; i <= n; i++)p[i] = i;

    //循环,从左岸开始,最大距离开始跳
    for(int i=0;i<n;++i){
        //可到达最大距离点 to
        int to = i + y;
        //如果可以直接到达右岸,那么就把i处的石子经过的次数加到s[n],也就是说可以从i点跳s[i]次到右岸s[n]
        if(to >= n){
            s[n] += s[i];
        }
        else{
            while (1)
            {
                //从 可到达点的高度(毕竟只能经过一定次数) 与 从该点出发的次数 找到最小
                int mi = min(a[to], s[i]);
                //将到达点的 可出发次数 加上mi次,有一次跳到这块石头就有一次出发
                s[to] += mi;
                //出发点的出发次数减少
                s[i] -= mi;
                //到达点的高度减少
                a[to] -= mi;
                
                //到达点高度为0,不能跳到这里了,就跳左边一个。这里就是并查集的用处,缩短距离,记录to这个点不能跳了,让后面的find去找更左边的点,避免重复找
                if(a[to] == 0){
                    p[to] = to -1;
                }
                //如果s[i]点出发次数为0,直接退出,从下一块石头开始跳
                if(s[i] == 0) break;

                //找到左边点的父节点
                to = find(to-1);
                //不能跳回去
                if(to <= i) break;

            }
            
        }
    }
    //只有右岸能跳2x次,说明该跳跃距离可以到达,后面二分继续测试最低的
    return s[n] == 2 * x;

}

int main(int argc, char const *argv[])
{
    
    //输入河长度n,上课天数x
    cin>>n>>x;
    //将石头高度输入到h中
    for(int i=1;i<n;++i){
        int m;
        cin>>m;
        h[i] = m;
    }
    //二分查找最低跳跃能力
    int l = 1, r = n;
	while (l < r) {
		int mid = (l + r) >> 1;
		if (!check(mid))
			l = mid + 1;
		else
			r = mid;
	}
    cout<<l;
    return 0;
}



posted @ 2023-03-10 09:03  哦、菜狗啊  阅读(64)  评论(0)    收藏  举报