codeforces 505E Mr. Kitayuta vs. Bamboos 题解
直接讲我做这题的思路吧
我的做法好像不太一样?
看到最小化最大值,感觉可以二分答案,答案也确实有单调性.. 然后看 check 怎么做。
先抛开每天砍几次的限制,二分最大的那棵的高度之后,我们可以得知每棵竹子总计要被砍多少长度,进而求出总次数,先把这个判掉,如果总次数之和大于 \(mk\),显然不行。

图中相当于把这个竹子分成四段,最优情况下,在没有第几天的限制时,我一定会让每一次砍操作都砍掉其中一段,总次数就是所有竹子的段数之和 \(sum\)。
注意到时间靠后的“砍”操作一定比时间靠前的更有价值,比如我对于同一个竹子,本来在第 \(k\) 天砍,把这次机会移到第 \(k+1\) 天后,这棵竹子要么长度减小要么不变,不会增加。
也就是说我可以把“砍”操作任意后移。所以我们可以贪心的让“砍”操作尽量在前面,然后在这样的最优方案下,保证按时间的每一个后缀和符合要求,即后 \(i\) 天的总操作数(砍的次数)\(\leq ik\)。每个后缀符合要求的话一定可以通过把“砍”操作后移使最终方案符合要求。
事实上不同的竹子可以相互独立考虑。于是给出贪心方法:我们枚举每个竹子,按时间顺序,只要当前长到长度大于等于 \(p\),就砍 \(p\),否则看长度是不是大于等于顶上那段 \(fir\),因为把 \(fir\) 砍掉也能使它少掉一次需要砍的次数,不过砍了就一定砍成 0,可能会把 \(fir\) 下面那段 \(p\) 砍去一部分,要注意一下这个。
为什么贪心正确:假设按该贪心算法某个竹子需要砍 \(x\) 次。在恰好砍 \(x\) 次的情况下这样砍一定是最优的方法。砍少于 \(x\) 次不可行。如果砍多于 \(x\) 次,可以证明对于任意砍多于 \(x\) 次的方案,一定不会有某个后缀 \(i\) 的总操作数比贪心方案更小。
这样复杂度是 \(O(nm\log W)\) 的,过不了,但是在枚举天数的时候,很多次枚举都是等它长到长度足够,这没有意义,把这些合并起来一起处理,就是每次让它一下直接长到能砍(当然对应的天数也要加上去)的高度。由于最多砍 \(mk\) 次(否则之前判掉了),时间复杂度降为 \(O((n+mk)\log W)\)。
using namespace std;
typedef long long LL;
const LL N = 200005;
LL n,m,k,p;
LL to[N],h[N],dv[N],fir[N],a[N];
LL val[5005];
LL chk(LL x){
LL sum = 0,td;
for(LL i = 1;i <= n;i ++){
to[i] = h[i] + a[i] * m - x;
fir[i] = -1; dv[i] = 0;
if(to[i] <= 0) continue;
dv[i] = (to[i] - 1) / p;
fir[i] = to[i] - dv[i] * p;
sum += (dv[i] + 1);
} if(sum > m * k) return 0; // 判总次数
for(LL i = 1;i <= m;i ++) val[i] = 0;
for(LL i = 1;i <= n;i ++){
to[i] = h[i];
for(LL j = 1;j <= m;){
while(to[i] >= p && dv[i]){
to[i] -= p; dv[i] --;
val[j] ++;
}
if(to[i] >= fir[i] && fir[i] != -1){
to[i] -= fir[i]; fir[i] = -1;
val[j] ++;
}
if(fir[i] == -1 && !dv[i]) break;
td = ((fir[i] == -1 ? p : fir[i]) - to[i] - 1) / a[i];
to[i] += a[i] * (td + 1); j += (td + 1);
}
} sum = 0;
for(LL i = m;i >= 1;i --){
sum += val[i];
if(sum > k * (m - i + 1)) return 0;
} return 1;
}
int main(){
ios::sync_with_stdio(false);
LL mxa = 0,l,r,mid,ans;
cin >> n >> m >> k >> p;
for(LL i = 1;i <= n;i ++){
cin >> h[i] >> a[i];
mxa = max(mxa,a[i]);
// 这是二分下界,最后一轮全部砍成 0 后,还是会再长一段,不取这个会错,见样例 2
}
l = mxa,r = 0x3f3f3f3f3f3f3f3f;
while(l <= r){
mid = (l + r) >> 1;
if(chk(mid)){
ans = mid;
r = mid - 1;
}
else l = mid + 1;
}
cout << ans << '\n';
return 0;
}

浙公网安备 33010602011771号