[BZOJ4415]发牌:线段树二分

题目中的销牌操作看起来不太好理解,但其实,这种操作和环形结构有着异曲同工之妙。我们维护一个变量now表示当前牌库顶的牌,每次销牌时,令now+=r[i],因为是环形结构,所以我们还要对now取模,模数为当前牌数。问题来了,每次销牌过后都要取走牌库顶的牌,因此模拟销牌操作时需要逐一判断当前的牌是否已经被取走(还要开一个vis数组),这样显然会超时。这时我们就需要转换思路,我们发现,改变now的值后,我们要取的牌就是当前牌库(假设我们已经取走了以前的牌)的第now张(我也不知道怎么发现的...)。怎么快速求出第now张牌呢?建立一棵权值线段树,在上面二分即可。详情请看代码。

#include<cstdio>
#define maxn 700010
#define reg register
using namespace std;
int n,now;
int num[maxn<<2];//权值线段树
int read()
{
	reg char ch=getchar();reg int in=0;
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch<='9'&&ch>='0') in=(in<<1)+(in<<3)+ch-'0',ch=getchar();
	return in;
}
void build(reg int x,reg int l,reg int r)
{
	if(l==r)
	{
		num[x]=1;return;
	}
	reg int mid=l+r>>1,ls=x<<1,rs=x<<1|1;
	build(ls,l,mid),build(rs,mid+1,r);
	num[x]=num[ls]+num[rs];
}
int query(reg int x,reg int l,reg int r,reg int k)
{
	num[x]--;//查询的同时进行修改,因为取走了一张牌
	if(l==r) return l;//找到了第k大
	reg int mid=l+r>>1,ls=x<<1,rs=x<<1|1;
	if(k<=num[ls]) return query(ls,l,mid,k);//二分查找
	return query(rs,mid+1,r,k-num[ls]);//如果第k大在右子结点,记得改变k的值
}
int main()
{
	n=read(),build(1,1,n);
	for(reg int i=n;i;i--)
	{
		now=(now+read())%i;
		printf("%d\n",query(1,1,n,now+1));
	}
	return 0;
}

主程序中有一个值得注意的细节:按理说应把now的初值赋为1(因为是从第1张牌算起而不是第0张),而且调用查找函数时应用query(1,1,n,now)。如果now=i那么now\ mod\ i=0,需要特判:if(!now)\ now=i。为了避免讨论,我们把now的初值赋为0,调用查找函数时query(1,1,n,now+1)即可。

猜你喜欢

转载自blog.csdn.net/pig_dog_baby/article/details/81538816
今日推荐