bzoj 3441: 乌鸦喝水(说实话有生之年没有见过这么难的模拟)

3441: 乌鸦喝水

Time Limit: 20 Sec   Memory Limit: 128 MB
Submit: 311   Solved: 117
[ Submit][ Status][ Discuss]

Description

【题目背景】
    一只乌鸦在自娱自乐,它在面前放了n个有魔力的水缸,水缸里装有无限的水。
【题目描述】
    他准备从第1个水缸飞到第n个水缸,共m次。在飞过一个水缸的过程中,如果他能够得着水缸里的水,即水缸口到水面距离小于等于乌鸦能够得着的深度,那它就会喝水缸里的水。每喝一次水,所有水缸里的水位都会下降,第i个水缸里的水位会下降Ai,注意喝水是瞬间的,如果乌鸦刚好够得着,但喝完之后够不着,也视为喝到一次,水位也会相应的下降。

Input

共有3行。第一行有三个正整数n、m和x,用空格隔开。n表示水缸的数量,m表示乌鸦飞的次数,x表示乌鸦能够得着的深度。第二行,有n个用空格隔开的正整数,第i个数为第i个水缸中水缸口到水面的距离Wi。第三行,有n个用空格隔开的正整数,第i个为Ai。

Output

只有一行,这一行只有一个正整数,为这只乌鸦能喝到水的次数。

Sample Input

5 2 20
15 14 13 12 12
1 1 1 1 1

Sample Output

9


对的没错这是道模拟题,一看题目就很容易看出来

题目很好理解,先计算出每个水缸口下降多少次后就再也喝不到了

s[x].val表示第x个水缸口下降s[x].val后就再也喝不到

因为每喝一次所有水缸口都会下降,所以s[].val的大小关系永远不变,并且每次都是当前s[x].val最小的最先喝不了

假设可以喝无数趟,这题就是水题,答案就是max(a[x].val)(1<=x<=n),很好理解因为你每次无论喝哪杯结果都一样,所以你喝的顺序无所谓,最后一定喝了max(a[x].val)(1<=x<=n)次

但是这题你最多跑m趟,这样就有关系了,因为你一趟喝多少水取决于你之前的情况

比如(s[x].val的)数据 1 2 3 4 5,你喝一趟显然能喝5次,但数据5 1 2 3 4你第一趟就只能喝1次了


所以这题每次喝都的前后状态有关,那么怎么写呢,看上面红字

这样就有个大概的思路:每次都看当前s[x].val最小的在第几趟时喝到第几个水缸口时喝完

直到喝完m趟或者所有的水缸口都喝不到结束


思路还算简单处理起来可麻烦

①先算出是在第几趟的时候喝完当前s[x].val最小的水缸口

这个整除下就好了,假设当前已经有p-1个水缸口喝不到水了,那么趟数就是(s[rak[p]].val-lost)/(n-(p-1)),其中rak[p]表示第p小的水缸口编号,lost表示已经整体下降了lost次(也就是乌鸦已经喝了lost次水)

②之后看喝到第几个编号的水缸时刚好喝完当前s[x].val最小的水缸

这里得用二分+线段树or树状数组判断,找到一个位置r满足r前面刚好有s[x].val个水缸口没有被喝完

这样喝到位置r时,当前s[x].val最小的水缸口刚好被喝完

②到这里可以判断下一个水缸口了

不过因为你这一趟还没有喝完,所以要先把这一趟完,注意r = min(r, x)!因为你不知道下一个水缸口在什么地方

有可能在x到r的范围内这样就有可能已经喝不到了,之后分情况讨论。。如果在min(r, x)前面那么肯定不影响,判断这一趟有没有喝完就好了,如果在后面需要继续二分,具体看代码吧

复杂度(nlognlogn)


#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define LL long long
typedef struct
{
	LL id;
	LL val;
}Res;
Res s[100005];
LL n, rak[100005], tre[100005];
bool comp1(Res a, Res b)
{
	if(a.val<b.val || a.val==b.val && a.id<b.id)
		return 1;
	return 0;
}
bool comp2(Res a, Res b)
{
	if(a.id<b.id)
		return 1;
	return 0;
}
void Update(LL x)
{
	while(x<=n)
	{
		tre[x]++;
		x += x&-x;
	}
}
LL Query(LL x)
{
	LL sum = 0;
	while(x)
	{
		sum += tre[x];
		x -= x&-x;
	}
	return sum;
}
int main(void)
{
	LL lost;
	LL m, p, x, i, now, temp, l, r, mid;
	while(scanf("%lld%lld%lld", &n, &m, &p)!=EOF)
	{
		for(i=1;i<=n;i++)
			scanf("%lld", &s[i].val);
		for(i=1;i<=n;i++)
		{
			scanf("%lld", &x);
			s[i].val = max((p-s[i].val)/x+1, 0ll);
			s[i].id = i;
		}
		sort(s+1, s+n+1, comp1);
		for(i=1;i<=n;i++)
			rak[i] = s[i].id;
		sort(s+1, s+n+1, comp2);
		p = 1, lost = 0;
		memset(tre, 0, sizeof(tre));
		while(m)
		{
			while(s[rak[p]].val-lost<=0 && p<=n)
				Update(rak[p++]);
			if(p>n)
				break;
			temp = min(m, (s[rak[p]].val-lost)/(n-p+1));
			lost += temp*(n-p+1);
			m -= temp;
			if(m==0)
				break;
			now = 1;
			while(now<=n)
			{
				while(s[rak[p]].val-lost<=0 && p<=n)
					Update(rak[p++]);
				temp = n-(now-1)-(Query(n)-Query(now-1));
				while(rak[p]<now && s[rak[p]].val-lost-temp<=0 && p<=n)
					Update(rak[p++]);
				if(p>n)
					break;
				if(s[rak[p]].val-lost-temp>0)
				{
					lost += temp;
					break;
				}
				temp = min(s[rak[p]].val-lost, rak[p]-(now-1)-(Query(rak[p])-Query(now-1)));
				lost += temp;
				l = 0, r = n-now+1;
				while(l<r)
				{
					mid = (l+r+1)/2;
					if(now-1+mid-(now-1)-(Query(now-1+mid)-Query(now-1))<=temp)
						l = mid;
					else
						r = mid-1;
				}
				now = now+r;
				Update(rak[p++]);
			}
			m--;
			if(p>n || m==0)
				break;
		}
		printf("%lld\n", lost);
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值