牛客14269 sum 线段树 数位

链接: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);
		}
		
	}
} 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值