竹子 题解
竹子 题解
赛题来自 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;
}