题意
有n根竹子,初始高度是h[i],每天结束时会长高a[i],每天你可以砍K刀,一刀能减小p的高度。可以在某一天内砍相同的竹子多次。问m天结束后,最高的竹子最矮是多高。
思路
- 题解第一种做法没看懂,写一下第二种
- 首先二分答案,判定是否存在方案:
- 考虑倒着做,问题变成了每天缩水一定高度,然后可以做k次长高操作。
- 中途不能有负高度,并且最后每根竹子的高度不能比一开始矮。
- 正确性的话,假如存在顺着做的方案让最终每根竹子高度比x小,那将这种方案最终竹子的所有高度调整至x,然后再倒过来,也是一种合法的倒着做方案。
- 另一方面,只要找到一种倒着做方案,也能反过来变成顺着做的方案。
- 因此,只需要判断是否存在倒着做的合法方案即可。
- 因为高度不会溢出,因此所有竹子加的次数都是一定的。只需要安排顺序使得不存在负数。
- 倒着决定每一刀放哪里,首先操作最快变成负数的竹子。直到所有竹子都在第0天为正后,再考虑让每一根竹子比h[i]大。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int n,m,k,p;
ll h[N], a[N], mx;
set<pair<ll,ll> > s;
ll now[N];
bool ok(ll x) {
s.clear();
for(int i = 1; i <= n; i++) {
now[i] = x - a[i];
s.insert(make_pair(m - (now[i] + a[i]) / a[i], i));
}
int can = 1;
for(int i = m; i; i--) {
if (s.rbegin() -> first >= i) return 0;
for(int j = 1; j <= k; j++) {
if (s.rbegin() -> first > 0) {
int u = s.rbegin() -> second;
s.erase(--s.end());
now[u] += p;
s.insert(make_pair(i - (now[u] - (m - i) * a[u] + a[u]) / a[u], u));
} else {
while(can <= n && now[can] - (m - i) * a[can] - a[can] * (i - 1) >= h[can]) {
can++;
}
if (can <= n) {
now[can] += p;
}
}
}
}
for(int i = 1; i <= n; i++)
if (now[i] - (m - 1) * a[i] < h[i]) return 0;
return 1;
}
int main() {
freopen("c.in", "r", stdin);
cin>>n>>m>>k>>p;
for(int i = 1; i <= n; i++) {
scanf("%I64d %I64d",&h[i], &a[i]);
mx = max(mx, a[i]);
}
ll L = mx, R = 1e9 * m + 1e9, ans = 0;
while (L <= R) {
if (ok(L + R >> 1)) {
R = ans = L + R >> 1;
R--;
} else L = (L + R >> 1) + 1;
}
cout << ans << endl;
}