青蛙哥种了 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 1≤n≤105,m≤5000,k≤10,0≤p,hi?,ai?≤109
首先二分答案 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 m?1 天,如果有竹子在接下来一天会变成负数,就给它加长度。到了第 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<bits/stdc++.h>
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;
}