蓝桥杯2022省赛C++A组 F题 青蛙过河 题解

题目描述

小青蛙住在一条河边,它想到河对岸的学校去学习。小青蛙打算经过河里的石头跳到对岸。
河里的石头排成了一条直线,小青蛙每次跳跃必须落在一块石头或者岸上。
不过,每块石头有一个高度,每次小青蛙从一块石头起跳,这块石头的高度就会下降 1,当石头的高度下降到 0 时小青蛙不能再跳到这块石头上(某次跳跃后使石头高度下降到 0 是允许的)。
小青蛙一共需要去学校上 x 天课,所以它需要往返 2x 次。当小青蛙具有一个跳跃能力 y 时,它能跳不超过 y 的距离。
请问小青蛙的跳跃能力至少是多少才能用这些石头上完 x 次课。

题解

首先思考一只青蛙怎么跳,发现青蛙每次都跳能力范围内的最远距离最优,这个贪心是显然的。

然后题目可以转化为 2 x 2x 2x 只青蛙一起跳,跳的策略同一只青蛙,也是贪心地跳。

那么既然策略知道了,我们可以考虑二分跳跃能力,每次check即可。

check的话,暴力做法是枚举青蛙的位置 i i i ,和跳的目标位置 j j j,如果 h j > 0 h_j > 0 hj>0 就跳,这样check一遍是 O ( n 2 ) O(n^2) O(n2) 的,总时间复杂度是 O ( n 2 log ⁡ x ) O(n^2\log x) O(n2logx)

一个明显的想法是我们可以用 set 维护 h j > 0 h_j > 0 hj>0 的石头,每次在 set 上二分直接找到目标位置 j j j ,这样总时间复杂度是 O ( n log ⁡ n log ⁡ x ) O(n \log n \log x) O(nlognlogx) 。队内大佬大多用这种方法。

我们维护 “一块石头不能跳后需要跳前面哪一块石头”,我们可以用并查集或者链表维护这个东西。这样总时间复杂度是 O ( n log ⁡ x ) O(n \log x) O(nlogx) 。wls用到是这种方法。

附上wls讲解视频。

wls锐评+讲解 2022蓝桥杯省赛C++ A组题目 全网首发_哔哩哔哩_bilibili

然后队内交流的时候有人提出这样一种check方法,记当前二分的跳跃能力为 m i d mid mid ,若所有长度为 m i d mid mid 的区间的和都大于等于 2 x 2x 2x,则可行,否则不可行。

这个结论一开始我们都觉得不靠谱,毕竟有 set 或者 并查集 这些更靠谱的方式,提出结论的人自己也是乱猜的。

但令人震惊的是,这种做法竟然能过所有测试数据。

于是我又赶紧把代码要过来对拍,发现拍了很多组都拍不出问题。说明这个结论很有可能是对的。

下面我尝试简单证明这个结论。

结论1:若所有长度为 m i d mid mid 的区间的和都等于 2 x 2x 2x,则可行。

因为所有区间和相等,所以对于每个 i i i ,满足 h i = h i + m i d h_i = h_{i + mid} hi=hi+mid ,所以我们先从岸边跳到第一个长度为 m i d mid mid 的区间,然后对于每个 i i i,我们跳到 i + m i d i + mid i+mid,一直这样跳最后可以跳到终点。

结论2:若所有长度为 m i d mid mid 的区间的和都大于 2 x 2x 2x,则可以忽略一些石头的高度,使最后所有长度为 m i d mid mid 的区间的和都等于 2 x 2x 2x

我们可以贪心地去构造一种忽略方式,从左往右扫,若当前区间和大于 2 x 2x 2x,则忽略(减去)当前高度。这样可以保证最后长度为 m i d mid mid 的区间的和都等于 2 x 2x 2x

结论3:若可行,则所有长度为 m i d mid mid 的区间的和都大于等于 2 x 2x 2x

对于一个长度为 m i d mid mid 区间为 [ l , r ] [l, r] [l,r],设状态1为 2 x 2x 2x 个青蛙位置小于 l l l ,状态2为 2 x 2x 2x 个青蛙位置大于 r r r。那么状态1的所有青蛙都不能直接跳到状态2,必须在 [ l , r ] [l, r] [l,r] 区间上停留至少1次,那么 [ l , r ] [l, r] [l,r] 的区间和就必须大于等于 2 x 2x 2x

用这三个结论可以证明充要条件。

那么有了这个结论,我们就不再需要二分,直接双指针扫一遍即可。

最终这道题时间复杂度是 O ( n ) O(n) O(n)


看到有大佬用线段树优化建图网络流过check过了F题,这样又多了一种check方法。

代码

二分+并查集

#include <bits/stdc++.h>
using std::cin;
using std::cout;
using std::cerr;

// #define DEBUG
// #define OIO
// #define FR

#ifdef DEBUG
#define debug(x) std::cerr << #x << ": " << (x) << "  "
#define debugl std::cerr << "\n"
#else
#define debug(x)
#define debugl
#endif

#ifdef OIO
#define oio
#else
#define oio std::ios::sync_with_stdio(false), std::cin.tie(nullptr)
#endif

#ifdef FR
#define frr freopen("1.in","r",stdin)
#define frw freopen("1.out","w",stdout)
#else
#define frr
#define frw
#endif

using i64 = long long;
#define int i64
#define double long double

struct DSU {
    std::vector<int> f, siz;
    DSU() : f(), siz() {}
    DSU(int n) : f(n), siz(n, 1) {
        iota(f.begin(), f.end(), 0);
    };
    int leader(int x) {
        while (x != f[x]) x = f[x] = f[f[x]];
        return x;
    }
    bool same(int x, int y) {
        return leader(x) == leader(y);
    }
    bool mergeto(int x, int y) {
        x = leader(x);
        y = leader(y);
        if (x == y) return false;
        siz[y] += siz[x];
        f[x] = y;
        return true;
    }
    int size(int x) {
        return siz[leader(x)];
    }
};

int n, x;
std::vector<int> h(int(1e5) + 1);

bool judge(int len) {
    DSU dsu(n + 1);
    std::vector<int> a(n + 1);
    a[0] = 2 * x;
    for (int i = 0; i < n; i++) {
        while (a[i] > 0) {
            int j = std::min(i + len, n);
            int jj = dsu.leader(j);
            if (jj <= i) {
                return false;
            }
            int dec = std::min(h[jj] - a[jj], a[i]);
            a[i] -= dec;
            a[jj] += dec;
            if (a[jj] == h[jj]) {
                dsu.mergeto(jj, jj - 1ll);
            }
        }
    }
    return true;
}


signed main() {
    oio;
    frr;

    cin >> n >> x;

    const int inf = 1e16;

    for (int i = 1; i < n; ++i) {
        cin >> h[i];
    }
    h[0] = inf, h[n] = inf;

    int l = 1, r = n;
    while (l < r) {

        int mid = (l + r) >> 1;
        if (judge(mid)) r = mid; else l = mid + 1;
    }

    cout << l;
    
    return 0;
}
  • 14
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
蓝桥杯是一个国内著名的计算机比赛,为了帮助参赛者更好地准备和了解比赛的型,委会会公布历年的真并提供相应的题解。 首先,我们需要了解蓝桥杯是一个综合性的计算机比赛,测试的对象包括计算机基础知识、编程能力以及解决实际问的能力。 在历年的真中,参赛者将面临不同类型的目,包括算法设计与优化问、数据结构与算法问、编程等。其中针对Python B目主要考察的是对Python语言的掌握和应用能力。 目解答一般会包含以下几个方面的内容: 1. 目分析与理解:读取目,理解目的要求和限制条件。通过仔细分析目,确定目的输入与输出,以及问的核心。 2. 设计解决方案:根据目要求和限制条件,设计一个合适的解决方案。可以使用合适的算法和数据结构来解决问,并做出相应的性能优化。 3. 编写代码实现:根据设计的方案编写相应的代码实现。需要注意的是,Python语言有其独特的语法和特性,掌握好这些特性可以更好地完成编程任务。 4. 调试与测试:编写完代码后,需要进行调试和测试。通过运行样例输入和输出,检查代码是否符合目要求,并且没有逻辑上的错误。 5. 总结与优化:在完成目解答后,可以进行总结和优化。包括分析算法复杂度、代码风格和可读性等方面,以便在比赛中更好地表现。 在准备蓝桥杯时,可以通过阅读历年的真题解来了解比赛的难度和类型,针对性地进行练习和提高。同时也可以参加相关的培训班和讨论活动,与其他参赛者交流经验和技巧。 总而言之,历年蓝桥杯的解答对于提高自己的编程能力和应对比赛非常有帮助。通过认真分析和实践,可以更好地理解并掌握Python编程,并在比赛中取得更好的成绩。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值