题目大意
拆位
涉及位运算的操作,通常需要拆位,不过这题稍有不同,因为括号里有个+x的操作。
我们可以把a[i]%2^i后加入第i个线段树里(权值线段树),然后对于一个&y的操作,我们就可以只考虑y的二进制下有1的位。
先忽略x
假如这一位为10000
那么找到对应的线段树,查询10000~11111的个数,我们就可以得到贡献了。
那么有x操作,naive的想法就是查询max(0,10000-x1)~11111-x1(x1=x%2^5)
然而如果x为1????,可能会出现a[i]+x的第5位都为1而进位,但是a[i]+x的后部分进位使得第五位仍然为1的情况,这个考虑起来也比较容易,对应的区间其实就是(10000*3-x1,11111)
用线段树常数较大(我是卡过的),推荐树状数组
代码
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fod(i,a,b) for(i=a;i>=b;i--)
#define ll long long
using namespace std;
const int maxn=100000+5;
const int ma=1<<20;
int i,j,n,q,t,two[21];
int tree[20][ma*4],a[maxn];
bool b;
void put(int k,int l,int r,int a){
if (b) tree[t][k]++;else tree[t][k]--;
if (l==r)return;
int m=(l+r)/2;
if (a<=m) put(k*2,l,m,a);else put(k*2+1,m+1,r,a);
}
int find(int k,int l,int r,int a,int b){
if (a>b) return 0;
if (l==a&&r==b) return tree[t][k];
int m=(l+r)/2;
if (b<=m) return find(k*2,l,m,a,b);else
if (a>m) return find(k*2+1,m+1,r,a,b);else
return find(k*2,l,m,a,m)+find(k*2+1,m+1,r,m+1,b);
}
int main(){
scanf("%d%d",&n,&q);
two[0]=1;
fo(i,1,20) two[i]=two[i-1]*2;
fo(i,1,n) {
scanf("%d",&a[i]);b=1;
fo(t,0,19) put(1,0,two[t+1]-1,a[i]%two[t+1]);
}
fo(i,1,q){
int o,x,y;
scanf("%d%d%d",&o,&x,&y);
if (o==1){
b=0;fo(t,0,19) put(1,0,two[t+1]-1,a[x]%two[t+1]);
a[x]=y;b=1;
fo(t,0,19) put(1,0,two[t+1]-1,a[x]%two[t+1]);
}else{
ll ans=0;
fo(t,0,19){
if (two[t]&y) {
int x1=x%two[t+1];
ll s=find(1,0,two[t+1]-1,max(0,two[t]-x1),max(-1,two[t+1]-1-x1));
if (x&two[t]) s+=find(1,0,two[t+1]-1,two[t]*3-x1,two[t+1]-1);
ans+=(ll)two[t]*s;
}
}
printf("%lld\n",ans);
}
}
}