竹子 题解

竹子 题解

赛题来自 OIFHA 第四场模拟赛。

原题展现

青蛙哥种了 \(n\) 棵竹子,一开始第 \(i\) 棵竹子的高度为 \(h_i\),每天会长高 \(a_i\)。由于竹子长得太快,青蛙哥不得不砍掉一些竹子,但是,每次只能砍下一截长度为 \(p\) 的竹子,而且为了防止刀具磨损,青蛙哥每天只能用刀砍 \(k\) 次。如果一个竹子的高度不足 \(p\),显然砍完之后高度不能为负数,而应该是 \(0\)

青蛙哥想知道,他砍了 \(m\) 天之后,最高的一棵竹子的最低高度是多少。每天先砍竹子,砍完后竹子才会生长。

Solution

思考一手,根据数据范围,先看看二分能不能解决:是否可能 \(m\) 天后竹子的高度都不超过 \(X\)

因为这个竹子被削减到 \(0\) 之后,不可能削减到负数,不好做。所以需要转换。

这道题难就难在转换。

考虑将“砍”和“长”的概念反过来:一开始竹子的高度都是 \(X\),每天都会“自然削减”\(a_i\),如果削减到负数,就失败;否则就可以在所有的竹子中选出 \(k\) 棵“拔高”\(p\)

通过这样的转换后,我们可以把一些拔高的机会积攒起来,留到以后使用。\(m\) 天后,看看是否所有的竹子高度都至少为 \(h_i\),决定了是否有可能成功。

这样一来,问题就清晰了很多。确定二分可做,可以开始考虑 check() 了。

  • 每天都有 \(k\) 次“拔高”机会,如果有竹子今天就要削减到负数,就“拔高”这棵竹子;没有机会了,就不行。多余的机会可以积攒。
  • \(m\) 天后,如果一棵竹子的高度 \(h'<h\),就要用 \(\lceil \frac{h'-h}{p}\rceil\) 次机会来补足 \(h\) 的高度。同上,如果没有机会了,就失败了。

至于“每棵竹子至多什么时候削减到负数”这个东西,可以用数组储存一下。说白了就是记录这棵竹子什么时候寄。

\[c_i=\lfloor \frac{X}{a_i}\rfloor \]

这个式子就不解释了,自己思考,非常显然。

那么第 \(i\) 天该“拔高“哪些竹子呢?

根据上文得到的 \(c_i\),可以丢进小根堆里。如果堆顶(快要寄的竹子)在当天就要“拔高”了,就开始使用机会次数;否则就积攒这些机会次数。

Code

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define pii pair<int, int>
#define ft first
#define sd second

const int MAXN = 1e5 + 5;

int n, m, k, p;
int h[MAXN], a[MAXN], b[MAXN], c[MAXN];

bool check(int md) {
    priority_queue<pii, vector<pii>, greater<pii>> q;
    for (int i = 1; i <= n; i++) {
        b[i] = md;
        c[i] = md / a[i];
        q.push({ c[i], i });
    }

    int day = 0;
    for (int i = 1; i < m; i++) {
        day += k;
        while (!q.empty() && q.top().ft == i) {
            int j = q.top().sd;
            q.pop();
            if (day <= 0)
                return 0;
            --day;
            b[j] += p;
            c[j] = b[j] / a[j];
            q.push({ c[j], j });
        }
    }

    day += k;
    for (int i = 1; i <= n; i++) {
        int hh = b[i] - a[i] * m;
        if (hh < h[i])
            day -= ceil(1.0 * (h[i] - hh) / p);
    }

    return day >= 0;
}

signed main() {
    scanf("%lld%lld%lld%lld", &n, &m, &k, &p);
    for (int i = 1; i <= n; i++) scanf("%lld%lld", &h[i], &a[i]);

    int l = a[1], r = a[1] * m + h[1];
    for (int i = 2; i <= n; i++) l = max(l, a[i]), r = max(r, a[i] * m + h[i]);

    int ans = -1, mid;
    while (l <= r) {
        mid = l + r >> 1;
        if (check(mid))
            ans = mid, r = mid - 1;
        else
            l = mid + 1;
    }

    printf("%lld\n", ans);

    return 0;
}
posted @ 2024-01-22 21:05  一棵油菜花  阅读(22)  评论(0编辑  收藏  举报