Description

【NOIP提高】Binary_NOIP

Solution

维护位运算的题目一般都枚举每一个二进制位,来维护每个二进制位的信息。
那么要and y,访问y的每个二进制为1的节点,看看对应的位置有多少个。
但是这一题有一个很棘手的问题:怎么样才能处理加上任意数然后做位运算操作。
我们可以知道一个二进制j的位置上是1,说明2j−1...2j−1这段位置上有值,如果要加上x然后判断这个区间是否有值的话,只用判断[2j−1−x...2j−1−x]这段区间是否有值就好了。
现在问题来了:查询区间[2j−1−x...2j−1−x],如果减成负数了怎么办?
其实如果有一个点值为i,加上了一个x,如果超出了边界2j−1,那么他会到哪里去?其实他相当于进了一位,但是还是会有剩余部分的(就像9+2,满了个10,在个数那里还剩了1个1),那么剩余的部分会从头开始,在0那里加上剩余部分直到无法加为止。
其实只用把左右边界(l,r),l=(l+2j)mod2j,r=(r+2j)mod2j,这样就相当于把左右边界在2j中循环。
那么得出的l,r如果l < r,这一段[l,r]直接树状数组上访问就可以了;如果l>r,那么就分两段[0,r]和[l,2j-1]。

树状数组不能访问0怎么办

本来是[0,2j−1],那么进入树状数组就给他的下标+1就好了。

Code

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=21;
int i,j,k,l,n,m;
ll ans1,ans;
int t[21][1<<21],a[100007],opt,x,y,da,r,q,l1,r1;
int er[maxn+1];
int lowbit(int x){
return (-x)&x;
}
void add(int x,int y,int z){
for(y=y+1;y<=er[x+1];y+=lowbit(y))
t[x][y]+=z;
}
void get(int x,int y,int z){
if(y<0)return;
for(y=y+1;y;y-=lowbit(y)){
ans+=z*t[x][y];
}
}
int zuo(int x,int y,int z){
x=x%z;
y-=x;
if(y<0){
y=(y+z)%z;
}
return y;
}
int main(){
scanf("%d%d",&n,&q);da=(1<<21)-1;
fo(i,1,maxn)er[i]=1<<i-1;
fo(i,1,n){
scanf("%d",&a[i]);
fo(j,1,20){
// if(a[i]<=er[j+2])
add(j,a[i]%er[j+1],1);
}
}
while(q--){
scanf("%d%d%d",&opt,&x,&y);
if(opt==1){
fo(j,1,20){
// if(a[x]<=er[j+2])
add(j,a[x]%er[j+1],-1);
}
fo(j,1,20){
// if(y<=er[j+2])
add(j,y%er[j+1],1);
}
a[x]=y;
}
else{
ans1=0;
fo(i,1,20){
if(!(y&er[i]))continue;
ans=0;
l=zuo(x,er[i],er[i+1]),r=zuo(x,er[i+1]-1,er[i+1]);
if(l>r){
get(i,er[i+1]-1,1);
get(i,l-1,-1);
get(i,r,1);
}
else{
get(i,r,1);
get(i,l-1,-1);
}
ans1+=(ll)ans*er[i];
}
printf("%lld\n",ans1);
}
}
}