1. 程式人生 > 實用技巧 >[CF505E] Mr. Kitayuta vs. Bamboos 題解

[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;
}
void Write(int x) {
    if(x < 0)  putchar('-'), x = -x;
    if(x == 0)  putchar('0');
    int stk[55], tp = 0;
    while(x) stk[++tp] = x % 10, x /= 10;
    for(int i = tp; i; i--)  putchar(stk[i] + '0');
}
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;
}