蓝桥杯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 
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;
}

你可能感兴趣的:(蓝桥杯,acm竞赛)