codeforces 505E Mr. Kitayuta vs. Bamboos 题解

直接讲我做这题的思路吧
我的做法好像不太一样?

看到最小化最大值,感觉可以二分答案,答案也确实有单调性.. 然后看 check 怎么做。

先抛开每天砍几次的限制,二分最大的那棵的高度之后,我们可以得知每棵柱子总计要被砍多少长度,进而求出总次数,先把这个判掉,如果总次数之和大于 \(mk\),显然不行。

t4NaX4.png
图中相当于把这个竹子分成四段,最优情况下,在没有第几天的限制时,我一定会让每一次砍操作都砍掉其中一段,总次数就是所有竹子的段数之和 \(sum\)

注意到时间靠后的“砍”操作一定比时间靠前的更有价值,比如我对于同一个竹子,本来在第 \(k\) 天砍,把这次机会移到第 \(k+1\) 天后,这棵竹子要么长度减小要么不变,不会增加

也就是说我可以把“砍”操作任意后移。所以我们贪心的让“砍”操作尽量在前面,然后在这样的最优方案下,保证按时间的每一个后缀和符合要求,即后 \(i\) 天的砍总操作数 \(\leq ik\)

我们先假定只能砍 \(sum\) 次,事实上,即使多砍,也没有任何用处,该不行的还是不行,本来行的可能弄成不行,这看起来很不对,实际上是对的。比如我本来第 \(k\) 天要砍一次,现在我在前面多砍了几次,然后我第 \(k\) 天就不砍了,这样看起来 \(k\) 的后缀和减少了,其实不是,第 \(k\) 天留下的竹子长度变长了,后面一定会有某一天多出来了一个操作,\(k\) 以后的操作次数增加了,这样 \(k\) 的后缀没变,后面某一天的后缀却变大了。

事实上不同的竹子是相互独立的,于是我们枚举每个竹子,按时间从小到大看,只要当前长到的长度大于等于 \(p\),就砍 \(p\),否则看长度是不是大于等于顶上那段 \(fir\),因为把 \(fir\) 砍掉也能使它少掉一次需要砍的次数,不过砍了就一定砍成 0,可能会把 \(fir\) 下面那段 \(p\) 砍去一部分,要注意一下这个。
其实上面那两种是一样的,我为啥要分类讨论呢。

这样复杂度是 \(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;
}
posted @ 2020-06-09 10:40  ItzInstallB  阅读(147)  评论(0编辑  收藏  举报