【牛客】暑假多校训练营2 H-0 and 1 in BIT 题解

传送门:0 and 1 in BIT
标签:思维

题目大意

假设有一个用二进制表示的正整数x,规定两种操作:A:将x在二进制下的0变成1,1变成0。B:令x=x+1,如果此时x的长度超过了n位,则去掉超过第n位的部分。给出一个长度为n的只由A和B组成的操作序列。再给出Q次询问,每次询问给出一个x和一个区间,你需要输出x经过区间中的操作后得到的结果。本题要求强制在线,故每次询问的区间并不是真实区间,你需要将上一次询问的答案与当前区间进行计算才能得到真实区间。
输入:一个正整数n,一个长度为n的操作序列,Q次询问(1<=n,Q<=2e5,1<=|x|<=50)。
输出:每次询问输出一个正整数表示答案。

算法分析

  • 有一说一,这题就算不要求强制在线也没有其它好的做法,强制在线倒是帮我们排除了莫队、分块等做法,那么我们能想到的算法就不多了。一看数据范围两个2e5,单次询问最多只能是log级别。那我们优先考虑二分和线段树,但区间是给定的,二分没有用武之地,由于操作区间不带修改,线段树也可以用前缀和替代。
  • 既然用不到log级别的算法,我们就思考O(1)的做法。我们现在能直接得到的信息只有A、B的个数以及它们的顺序。如果答案与顺序有关,我们就要O(n)遍历,显然是行不通的。那我们就假设答案只与A、B的个数相关,从中找到规律。我们先考虑最简单的情况:设x为0,操作区间内容为ABA,那么0先取反为1,再加一为10,最后取反为01。如果x为1,操作结果则是0。
  • 我们很容易发现其中的规律:众所周知对于有m位的正整数x取反的值为(1<<m)-1-x,记其中的(1<<m)-1为y,那么先取反再+1再取反就相当于y-(y-x+1)=x-1。这时候你会问0的操作结果为什么是1而不是-1呢?这是因为取反后的x加一后从一位变成了两位,也就是说超过了m位就不再适用。要解决这个问题也很简单,我们只要像处理哈希值一样对其进行(x%mod+mod)%mod的操作就行了。
  • 现在的做法就很明确了,我们只要统计区间中A的个数是奇数还是偶数就知道最后是x还是y-x,再用前缀和存操作B得到的结果。前缀和的具体操作是:定义一个符号变量flag初始为1,如果当前操作为A,则将其取相反数,如果当前操作为B,则令前缀和加上flag*1。但这样还不够,因为截取一段区间时左端点的flag可能为负数,直接计算可能会导致答案相反。我们还需要开一个sign数组来记录每个下标的符号,计算时乘上其值即可负负得正。这样就实现了单次查询O(1)。

代码实现

#include <iostream>
using namespace std;
#include <cmath>
#include <cstring>
#include <assert.h>
int sum[400005],cnt[400005],sign[400005];
char str[400005],str1[400005];
int main(){
	int mi,flag=1;
	long long ans=0,ii,a,b,l,r,l1,r1,c,x,x1,z,y,i,j,n,m,T,k;
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	for(i=1;i<=n;i++)
		cin>>str[i];
	for(i=1;i<=n;i++){
		if(str[i]=='A'){
			sum[i]=sum[i-1];
			flag=-flag;
		}
		else sum[i]=sum[i-1]+flag;
		cnt[i]=cnt[i-1]+(str[i]=='A');
		sign[i]=flag;
	}
	sign[0]=1;
	for(i=0;i<m;i++){
		cin>>l>>r>>str1;
		int len=strlen(str1);
		x=0;
		for(j=0;j<len;j++)
			if(str1[j]=='1')
				x+=(1LL<<(len-1-j));
		l1=min((ans^l)%n+1,(ans^r)%n+1);
		r1=max((ans^l)%n+1,(ans^r)%n+1);
		y=(sum[r1]-sum[l1-1])*sign[l1-1];
		z=cnt[r1]-cnt[l1-1];
		x1=(1LL<<len);
		x=(x+y%x1+x1)%x1;
		if(z&1LL)x=x1-1-x;
		ans=x%x1;
		for(j=len-1;j>=0;j--)
			if(ans&(1LL<<j))
				cout<<1;
			else cout<<0;
		cout<<'\n';
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值