[CF505E] Mr. Kitayuta vs. Bamboos 题解
Description
给定 \(n\) 个数 \(h_{1 \dots n}\)。
你需要进行 \(m\) 轮操作,每轮操作为 \(k\) 次修改,每次修改可以选择一个数 \(h_i\) 修改为 \(\max(h_i - p, 0)\)。
每轮操作后每个 \(h_i\) 将会被修改为 \(h_i + a_i\)。
你需要最小化最终 \(h_{1 \dots n}\) 中的最大值。
\(n \le 10^5,m \le 5 \times 10^3,k \le 10\)。
Sol
最小值最大,首先想到二分答案,那么我们可以二分一个 \(H\) ,代表最终所有竹子都不高于 \(H\),考虑如何验证。
由于有 \(\max(h_i-p,0)\) 的存在,我们不好得知一次可以减少多少,那么不妨换一种角度考虑:
我们考虑让时间倒流,那么我们看上面的限制条件变成了什么:
-
最终所有竹子都不高于 \(H\) \(\Longrightarrow\) 竹子的初始长度为 \(H\),最终都不小于 \(h_i\)。
-
每轮操作后每个 \(h_i\) 将会被修改为 \(h_i + a_i\) \(\Longrightarrow\) 每次竹子的高度将会降低 \(a_i\),由于竹子的高度非负,所以竹子在降低后高度不能低于 \(0\)。
-
每次修改可以选择一个数 \(h_i\) 修改为 \(\max(h_i - p, 0)\) \(\Longrightarrow\) 每次可以将竹子的高度拔高 \(p\)。
观察到每次的 \(p\) 相同,那么我们可以贪心的拔高竹子。
对于上面提到的两个限制,我们可以这样操作:
先按照时间倒序处理,用一个堆存储所有在当前高度需要继续增加才能在结束时高度 \(\ge h_i\) 的竹子,按照小于 \(0\) 的时间排序,每次优先把最快 \(<0\) 的竹子拿出来增加 \(p\),如果在过了第 \(i\) 天后还有竹子在第 \(i\) 天之前高度会小于 \(0\),那么就不满足第二条内的限制条件,如果在最后还有竹子在堆里面,就代表有竹子的高度不能够 \(\ge h_i\),如果都满足,那么就代表我们二分出的高度可行。
时间复杂度 \(O(mk\log\max{h_i+ma_i})\)
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int Read() {
int x = 0, f = 1; char ch = getchar();
while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x * f;
}
int n, m, k, p, l, r, h[100005], a[100005], c[100005];
bool chk(int x) {
priority_queue<pair<int, int> > q;
memset(c, 0, sizeof(c));
for(int i = 1; i <= n; i++)
if(x < a[i] * m + h[i]) q.push(make_pair(-1 * (x / a[i]), i));
for(int i = 1; !q.empty() && i <= m; i++) {
for(int j = 1; !q.empty() && j <= k; j++) {
int u = q.top().second, v = -q.top().first;
//cout << v << " " << u << endl;
q.pop();
//cout << -q.top().first << " " << q.top().second << endl;
if(v < i) return 0;
c[u] += 1;
if(x + c[u] * p < a[u] * m + h[u])
q.push(make_pair(-1 * ((x + c[u] * p) / a[u]), u));
}
}
if(!q.empty()) return 0;
return 1;
}
signed main() {
n = Read(), m = Read(), k = Read(), p = Read();
for(int i = 1; i <= n; i++) h[i] = Read(), a[i] = Read(), r = max(r, h[i] + a[i] * m);
l = 0; int ans = -1;
chk(11);
while(l <= r) {
int mid = (l + r) >> 1;
if(chk(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
cout << ans << endl;
return 0;
}