[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;
}
posted @ 2020-10-29 15:42  verjun  阅读(110)  评论(0编辑  收藏  举报