bzoj 3441

首先庆祝自己开始玩博客啦大笑 大笑
然后进入正题。 bzoj 3441 乌鸦喝水。
题目是说有n水缸里有水,每喝一个水缸,全部的水缸里面的水都要下降,降到一定程度就不能喝到了,问喝m轮 可以喝多少次水。
看一下,我们首先一定会做同一步——处理出每个水缸降了多少次水后就不能再喝了。然后对于每一轮,我们假设可以喝的水缸有s个,那么每个水缸的次数都会减少s次,所以很显然我们只要抓住每个水缸喝完前被喝了多少次,和还剩下多少水缸有水即可。
值得一提的是:水缸次数少的一定会在次数多的前面喝完,抓住这个性质,我们把水缸次数排个序,然后一个一个处理过去即可,对于每一次处理,我们只需要二分查找找到第i个水缸在第i-1个水缸喝完后再经过cnt轮才快喝完(详见代码)。

所以时间复杂度是O(n log(n)+n log(m))

#include<cstdio>
#include<algorithm>
#define lowbit(i) i&(-i)
#define LL long long
using namespace std;
const int N=1E5+500;
LL a[N];
LL num[N],b[N],b1[N];
int n,m,tot,c[N];
LL q[N],ans;
void add(int x){
	for(int i=x;i<=n+1;i+=lowbit(i)) b[i]++;
}
int sum(int x){
	int tmp=0;
	for(int i=x;i>0;i-=lowbit(i))tmp+=b[i];
	return tmp;
}//树状数组存在的意义就是看当前位置的水缸在剩余水缸的位置中排第几个。
int j1;
LL find(LL jk,LL s){
	LL l=0,r=m-j1;
	while(l<r-1){
		int mid=(l+r)/2;
		if(mid*s>jk) r=mid;
		else l=mid;
	}
	if(r*s<=jk) return r;
	return l;
}
bool cmp(int x,int y){
	return q[x]<q[y]||q[x]==q[y]&&x>y;
}
int main(){
	freopen("1.in","r",stdin);	freopen("1.out","w",stdout);
	LL x;
	scanf("%d%d%lld",&n,&m,&x);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++){
		LL o;
		scanf("%lld",&o);
		a[i]=((x)-a[i])/o+1;
		if(a[i]>0) q[++tot]=a[i],c[tot]=tot; //求出次数大于0的水缸,并把下标记下。
	}
	LL op=0,s=tot;
	sort(c+1,c+tot+1,cmp);//将下标按照次数从小到大排序,若两个水缸次数一样多,那么后面的一定对前面的没影响,所以后面排到前面。
	for(int i=1;i<=tot;i++){
		LL cnt=find(q[c[i]]-op,s);//二分查找找到一个值使得 cnt*(当前剩余水缸数)<=该水缸还能喝的次数<(cnt+1)*(当前剩余水缸数),那么这个水缸除了前面的j1次喝水还至少能喝cnt次水。
		if(cnt+j1==m){
			ans+=(tot-i+1)*m;
			printf("%lld",ans);
			return 0;
		}//如果当前水缸可以喝m次那么后面的都可以做到。
		if(q[c[i]]-op-s*cnt-c[i]+sum(c[i])>=0){
			num[c[i]]=cnt+j1+1;
			op++;
		}//如果喝了cnt+j1轮水后,虽然不够再减一轮,但是可能还剩下一些次数足够在cnt+j1+1轮中喝。
		else num[c[i]]=cnt+j1;
		op+=cnt*s; s--; j1+=cnt;//维护j1和op,j1是喝了多少轮,op是喝了j1轮后,剩余水缸减了多少次。
		add(c[i]);
		ans+=num[c[i]];//将第i个水缸的喝的次数计入答案。
	}
	printf("%lld",ans);
}
其实cnt可以通过除法直接除出来 Orz 拉大爷

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值