竹子(?)

青蛙哥种了 n n n 棵竹子,一开始第 i i i 棵竹子的高度为 h i h_i hi,每天会长高 a i a_i ai。由于竹子长得太快,青蛙哥不得不砍掉一些竹子,但是,每次只能砍下一截长度为 p p p 的竹子,而且为了防止刀具磨损,青蛙哥每天只能用刀砍 k k k 次。如果一个竹子的高度不足 p p p,显然砍完之后高度不能为负数,而应该是 0 0 0

青蛙哥想知道,他砍了 m m m 天之后,最高的一棵竹子的最低高度是多少。每天先砍竹子,砍完后竹子才会生长。

1 ≤ n ≤ 1 0 5 , m ≤ 5000 , k ≤ 10 , 0 ≤ p , h i , a i ≤ 1 0 9 1\le n\le10^5,m\le5000,k\le10,0\le p,h_i,a_i\le10^9 1n105,m5000,k10,0p,hi,ai109


首先二分答案 X X X,判断 m m m 天之后是否所有竹子高度不超过 X X X

由于砍的长度不一定为 p p p,贪心不可行。考虑把全过程反过来看:一开始竹子高度都不超过 X X X(实际上就是等于 X X X),每天竹子会缩短 a i a_i ai 长度(如果长度为负,就不合法),然后你可以给竹子加 k k k 次长度 p p p m m m 天后,竹子高度至少为 h i h_i hi

现在贪心就好做了。首先每天的 k k k 次操作可以统筹分配,分到其他天。前 m − 1 m-1 m1 天,如果有竹子在接下来一天会变成负数,就给它加长度。到了第 m m m 天,如果竹子高度小于 h i h_i hi,也要加长度。

实现上可以用优先队列维护竹子在哪天需要加长度。到了第 i i i 天就处理优先队列队头的数据。

时间复杂度 O ( ( n + m k ) log ⁡ n log ⁡ V ) O((n+mk)\log n\log V) O((n+mk)lognlogV) V V V 是值域。

#include
using namespace std;
#define ll long long
const int N=1e5+1;
const ll INF=1e18;
ll n,m,k,p,h[N],a[N],b[N],c[N];
ll la[N<<2],Max[N<<2],Maxnum[N<<2];
bool check(ll mid)
{
    priority_queue<pair<ll,ll>,vector<pair<ll,ll> >,greater<pair<ll,ll> > > q;
    for(int i=1;i<=n;i++) b[i]=mid;
    for(int i=1;i<=n;i++) c[i]=mid/a[i],q.push(make_pair(c[i],i));
    ll num=0;
    for(int i=1;i<m;i++){
        num+=k;
        while(q.top().first==i){
            int id=q.top().second;
            q.pop();
            if(!num) return 0;
            num--;
            b[id]+=p;
            c[id]=b[id]/a[id];
            q.push(make_pair(c[id],id));
        }
    }
    num+=k;
    for(int i=1;i<=n;i++){
        ll sum=b[i]-a[i]*m;
        if(sum<h[i]) num-=(h[i]-sum+p-1)/p;
    }
    return num>=0;
}
int main()
{
    // freopen("bamboo.in","r",stdin);
    // freopen("bamboo.out","w",stdout);
    cin.tie(0)->sync_with_stdio(0);
    cin>>n>>m>>k>>p;
    ll l=0,r=2e15,ans;
    for(int i=1;i<=n;i++) cin>>h[i]>>a[i],l=max(l,a[i]),r=max(r,a[i]*m+h[i]);
    while(l<=r){
        ll mid=l+r>>1;
        if(check(mid)) r=mid-1,ans=mid;
        else l=mid+1;
    }
    cout<<ans;
}

你可能感兴趣的:(算法,数据结构)