传送门: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;
}