Description
有n个竹子,第i个竹子长度为h[i],每天的结束会长高a[i]
现在有m天,每一天可以做k次操作,每次操作可以选择一个竹子砍掉p,即高度h[i]=max(h[i]-p,0)
你需要最小化m天结束后最高的竹子的高度
n<=100000,m<=5000,k<=10
Solution
先考虑二分答案ans,然后有两种做法:
Solution 1:
考虑每个竹子,我们至少需要砍
t
[
i
]
=
m
a
x
(
0
,
⌈
h
[
i
]
+
m
∗
a
[
i
]
−
a
n
s
p
⌉
)
t[i]=max(0,\lceil {h[i]+m*a[i]-ans\over p}\rceil)
t[i]=max(0,⌈ph[i]+m∗a[i]−ans⌉)次
显然我们没有必要砍更多次
若∑t[i]>m*k显然是非法,所以我们可以依次判定每一刀
我们可以求出
d
i
,
j
d_{i,j}
di,j表示,若要在第i根竹子砍第j刀,至少需要在第
d
i
,
j
d_{i,j}
di,j天
考虑把这一刀挂在第
d
i
,
j
d_{i,j}
di,j天,从前往后扫每一天,能砍就砍,最后能砍完就合法
Solution 2:
考虑倒过来,所有竹子初始高度为ans,每一天所有竹子会先变矮a[i],然后每次操作选择一个竹子拔高p
限制是操作过程中所有竹子的高度非负
那么每次操作肯定选最快到0的竹子拔高,用一个堆来维护即可
判定的条件是每个竹子的高度>=h[i]
Code
这里是第一种解法:
#include <set>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define mp(a,b) make_pair(a,b)
using namespace std;
typedef long long ll;
int read() {
char ch;
for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
int x=ch-'0';
for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x;
}
const int N=1e5+5;
int n,m,k,p,cnt[N];
ll l[N],h[N],a[N];
bool check(ll x) {
ll tot=0;
fo(i,1,n) l[i]=h[i]+a[i]*m,tot+=max(0ll,(l[i]-x+p-1)/p);
if (tot>m*k) return 0;
fo(i,0,m) cnt[i]=0;
fo(i,1,n)
if (l[i]>x)
for(ll tmp=(l[i]-x-1)%p+1;tmp<=l[i]-x;tmp+=p)
if (tmp<=h[i]) cnt[0]++;
else if (tmp>h[i]+a[i]*(m-1)) return 0;
else cnt[(tmp-h[i]+a[i]-1)/a[i]]++;
int ret=0;
fo(i,0,m-1) {
ret+=cnt[i];
ret=max(0,ret-k);
}
return !ret;
}
int main() {
n=read();m=read();k=read();p=read();
fo(i,1,n) h[i]=read(),a[i]=read();
ll l=0,r=1e15,ans=0;
while (l<=r) {
ll mid=l+r>>1;
if (check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
printf("%lld\n",ans);
return 0;
}