链接:https://ac.nowcoder.com/acm/problem/14269
来源:牛客网
考虑维护一个这样的问题:
(1) 给出一个数组A,标号为1~n
(2) 修改数组中的一个位置。
(3) 询问区间[l,r]中所有子集的位运算and之和mod(109+7)。
位运算and即为“pascal中的and”和“C/C++中的&”
我们定义集合S={ l , l+1 , … , r-1 , r}
若集合T,T ∩ S = T,则称T为S的子集
设f(T)=AT1 and AT2 and … and ATk (设k为T集大小,若k=0则f(T)=0)
所有子集的位运算and之和即为∑f(T)
那么,现在问题来了。
题解:要维护的数据结构包括区间查询,单点修改,可以考虑用线段树。那么如何维护线段树呢?关键在于如何处理子集。这里要用到数位的知识,不妨按位考虑由于是& ,所以我们只用考虑区间中在该位上有多少1就行了。然后推一推关系就行。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
const int M=1e9+7;
ll a[N];
int n,L=32,m,opt;
struct node{
ll b[33];
node(){
memset(b,0,sizeof b);
}
}T[N<<2];
void build(int now,int l,int r){//维护每一位的个数
if(l==r){
for(int j=L;j>=0;j--){
if(a[l] & (1ll<<j)) T[now].b[j]=1;
}
return ;
}
int mid=(l+r)>>1;
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
for(int j=L;j>=0;j--)
T[now].b[j]=T[now<<1].b[j]+T[now<<1|1].b[j];
}
void change(int now,int l,int r,int p,ll x){
if(l==p & p==r){
// memset(T[now])
for(int j=L;j>=0;j--)
if(x & (1ll<<j)) T[now].b[j]=1;
else T[now].b[j]=0;
return ;
}
int mid=(l+r)>>1;
if(p<=mid) change(now<<1,l,mid,p,x);
if(mid+1<=p) change(now<<1|1,mid+1,r,p,x);
for(int j=L;j>=0;j--)
T[now].b[j]=T[now<<1].b[j]+T[now<<1|1].b[j];
}
node query(int now,int l,int r,int x,int y){
if(x <=l && r<= y){
return T[now];
}
int mid=(l+r)>>1;
// ll ans=0;node n1
node ans1=node();node ans2=node();
if(x<=mid) ans1=query(now<<1,l,mid,x,y);
if(mid<y) ans2=query(now<<1|1,mid+1,r,x,y);
for(int j=L;j>=0;j--)
ans1.b[j]+=ans2.b[j];
return ans1;
}
ll pow_mod(ll a,int b){
ll ans=1;
while(b){
if(b&1) ans=ans*a%M;
b>>=1;a=a*a%M;
}
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
build(1,1,n);
scanf("%d",&m);
while(m--){
int x,y;
scanf("%d%d%d",&opt,&x,&y);
if(opt==1){
change(1,1,n,x,y);
}else{
node tmp=query(1,1,n,x,y);
// ans%=M;
ll ans=0;
for(int j=L;j>=0;j--){
ans=(ans+(1ll<<j)*((pow_mod(2,tmp.b[j]))-1+M)%M)%M;
}
printf("%lld\n",ans);
}
}
}