【Codeforces 505E】Mr. Kitayuta vs. Bamboos

贪心,二分答案

题目大意:

给定 $n$ 个竹子,每个竹子有初始高度 $h_i$,并会在每个晚上增加 $a_i$ 的高度。

一共有 $m$ 天,每天白天可以砍 $k$ 次,每次可以砍掉一棵竹子(可以重复砍) $p$ 的长度(如果剩下的长度不足 $p$ 则砍光为止)。

求 $m$ 天过后,最长的竹子最短能够是多少。

题解:

考虑二分答案。

每次判断答案可行性。

我们倒着考虑,令当前二分的答案为 $H$,则令最后一天结束时的高度都为 $H$。

然后就相当于,每天竹子的高度会先减少 $a_i$,然后再选择 $k$ 根竹子,将其拔高 $p$。注意先后。

最后,要求每个竹子的高度都大于等于它原来的 $h_i$,则说明可行。并且中途不能有竹子高度小于 $0$。

贪心,计算出每棵竹子到什么时候,高度会变成负数。每次取变成负数最快的那个,将其拔高。

用堆维护。

如果一棵竹子在某次拔高后(或一开始),在规定时间内永远都不低于 $h_i$ 则不插回去。

正确性的话,我们肯定先得处理掉那些会变成负数的,而且越早的显然要越早处理掉。然后再把会低于 $h_i$ 的变高。变到满足条件就不再理会它了。

时间复杂度 $O(mk\log n\log w)$。

Code:

#include<iostream>
#include<queue>
using namespace std;
typedef long long LL;
const int N=1e5+6;
int n,m,k,p,h[N],a[N];
struct data{
    int id,s,t;
    LL H;
    inline bool operator<(const data&rhs)const{
        return t>rhs.t;
    }
    inline void update(int tim){
        H-=(tim-s)*(LL)a[id];
        s=tim;
        t=tim+H/a[id];
    }
};
priority_queue<data>q;
bool check(LL H){
    while(!q.empty())q.pop();
    for(int i=1;i<=n;++i)if(H-(LL)m*a[i]<h[i]){
        data s=(data){i,1,0,H};
        s.update(1);
        q.push(s);
    }
    for(int T=1;T<=m;++T)
        for(int x=1;x<=k;++x){
            if(q.empty())return 1;
            if(q.top().t<=T)return 0;
            data t=q.top();
            q.pop();
            t.H+=p;
            t.update(T);
            if(t.H-(LL)a[t.id]*(m-T+1)<h[t.id])q.push(t);
        }
    return q.empty();
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m>>k>>p;
    for(int i=1;i<=n;++i)
        cin>>h[i]>>a[i];
    LL l=0,r=1e18,ans=1e18;
    while(l<=r){
        const LL mid=l+r>>1;
        if(check(mid))r=(ans=mid)-1;else l=mid+1;
    }
    cout<<ans<<endl;
    return 0;
}