CF506C Mr. Kitayuta vs. Bamboos

27 篇文章 0 订阅
17 篇文章 0 订阅

CF506C Mr. Kitayuta vs. Bamboos

  • n n n个竹子,第 i i i棵竹子第一天之前的高度是 h i h_i hi,每一天的末尾会长高 a i a_i ai
  • 每一天你可以将砍 k k k刀,每一刀将一个竹子砍 p p p,如果砍的竹子高度小于 p p p,那么将会被砍到 0 0 0.
  • m m m天之后最高的竹子最矮是多少。
  • 注意每一天是先砍竹子、再长竹子。
  • n ≤ 1 e 5 , m ≤ 1 e 4 , k ≤ 10 n\le1e5,m\le1e4,k\le10 n1e5,m1e4,k10,所有读入的数 ≤ 1 e 9 \le1e9 1e9

Solution

  • 考虑二分答案 a n s ans ans。然后有两种方法,这里重点介绍solution1:
  • 显然每一个竹子最多砍 c = m a x ( 0 , ⌈ h i + a i m − a n s p ⌉ ) c=max(0,\lceil\frac{h_i+a_im-ans}{p} \rceil) c=max(0,phi+aimans)刀,我们求出这 c c c刀最早在什么时候砍,那么第一刀可以不砍满,后面的每一刀都要砍满,这样可以对于这个竹子让每一刀尽量往前排,然后要求这个竹子的每一刀都不能在这一刀之前砍,然后前往后扫一遍就可以了。
  • 这个方法看起来就非常的假,为什么不能将某一刀均摊到前面的若干刀呢,而且如果预计的某一刀往后挪了,那么相当于浪费少了,后面的一刀的最早出现时间可不可以提前呢?
  • 让我们来证明一下这个方法的正确性。
  • 首先考虑第一刀之前能不能多砍一刀。根据对于第一刀的定义,往前砍并不能使得 ( h i + a i m − a n s ) % p ≤ 0 (h_i+a_im-ans)\%p\le0 (hi+aimans)%p0,即对于后面的刀数没有影响(也就是后面原本要砍多少刀,刀现在还是要砍),并且为了让下一刀最早,如果往前砍了,下一刀最早还是会落在上述的第一刀上,所以第一刀之前肯定不能砍。
  • 考虑之后的几刀之前能不能砍,同理对于后面的刀数没有影响,按照原来的排列才能让每一刀最早能动。
  • 最后考虑如果之前的某一刀被迫后移了,同样对于刀数没有影响,但是可能会使得后面排列整体后移(因为浪费少了),但是最早出现在原先的地方肯定是没有问题的。
  • 综上,当且仅当在这些位置之后砍是最优的。

  • 第二种方法就是反过来做,拔高竹子,贪心地取小于 0 0 0的竹子,最后判定竹子的高度比初始高度要高即可。

Solution1:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define maxn 100005
#define ll long long 
using namespace std;

int n,m,K,i,j,k;
ll h[maxn],v[maxn],p,s[maxn];

int check(ll lim){
	ll cnt=0;
	for(i=1;i<=n;i++) cnt+=max(0ll,(h[i]+v[i]*m-lim+p-1)/p);
	if (cnt>m*K) return 0;
	for(i=0;i<=m;i++) s[i]=0;
	for(i=1;i<=n;i++) if (h[i]+v[i]*m>lim){
		for(ll res=(h[i]+v[i]*m-lim)%p;res<=h[i]+v[i]*m-lim;res+=p) if (res){
			if (res<=h[i]) s[1]++; else 
			if ((res-h[i]+v[i]-1)/v[i]+1>m) return 0; else 
				s[(res-h[i]+v[i]-1)/v[i]+1]++;
		}
	}
	cnt=0;
	for(i=1;i<=m;i++){
		cnt+=s[i];
		cnt=max(0ll,cnt-K);
	}
	return !cnt;
}

int main(){
//	freopen("ceshi.in","r",stdin);
	scanf("%d%d%d%lld",&n,&m,&K,&p);
	for(i=1;i<=n;i++) scanf("%lld%lld",&h[i],&v[i]);
	ll l=0,r=0,mid,ans;
	for(i=1;i<=n;i++) r=max(r,h[i]+v[i]*m);
	while (l<=r){
		mid=(l+r)>>1;
		if (check(mid)) 
			ans=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%lld",ans);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值