ldu-自测二-A,B(位运算符+位运算线段树(32树))

自测二的题都是数论,推出来题就很简单,推不出就发呆吧。淦!

众所周知,int范围内,搞位运算符一般开到32就够了。

A题

给定一个长为n的数组,求解1到n内所有子集的位运算与之和mod(109+7),与即为c++中的&
Input
第一行为n,n<=1e5,接下来n个数为a[i]<=1e9
Output
输出结果取余(1e9+7)

input
3
1 2 3
output
9
input
3
2 3 3
output
17

首先,我们很容易想到去对二进制下每一位分别讨论。

枚举当前位k(假设是第k位),假设共有x个数当前位上为1,则有(n−x)个数当前位上为0。

0的情况:只要(n-x)中任选一个,与运算后都为0,所以贡献的值也为0,所以可以不用去考虑。

1的情况:因为它是 “与” &,当前位上为1的x个数,如果不是这x个都不选,都可以“与”出一个1,贡献 2^k 的值,这样一共有 2 ^ x-1种情况,所以第k位贡献的值为 ( 2 ^ k)*(2 ^ x- 1)。(x表示共有x个数当前位k上为1)

所以遍历每个数,得到每一二进制位有多少个,运算上述关系式相加即可。

#include 
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const ll mod=1e9+7;
const int maxn=100+7;
ll n,m,x,c,y,mx;
ll sum;
ll a[maxn];
ll read(){
     
    ll x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
     
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
     
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
    return x*f;
}
ll qpow(ll a, int b){
     
	ll ans=1;
	while(b){
     
		if(b&1) ans=(ans*a)%mod;
		a=(a*a)%mod;
		b>>=1;
	}
	return ans;
}

int main(){
     
	n=read();
	for(int i=0;i<n;i++){
     
		x=read();	c=0;
		while(x){
     
			if((x&1))	a[c]++;
			x>>=1;	c++;
		}
		mx=max(mx,c);
	}
	for(int i=0;i<mx;i++){
     
		if(!a[i])	continue;
		sum=(sum+((qpow(2,i)*(qpow(2,a[i])-1)))%mod)%mod;
//		cout<
	}
	printf("%lld",sum);
}

B题

给定一个长为n(n<=1e5)a[i]<=1e9的数组,给定两种操作:

id == 1:输入pos,val,将a[pos]的值修改为val
id == 2:输入l,r。求解区间l到r中所有子集的&和%(1e9+7)。

Input
第一行为n,m

第二行n个数代表a[i]

然后m次操作
修改操作: 1 x y,将Ax修改为y
询问操作: 2 l r,区间[l,r]中所有子集的位运算and之和 mod(109+7)

Output
对于每次询问输出一行,为该次询问的答案mod(109+7)。

input
3 6 
1 2 3
2 1 3
1 1 2
2 1 3
2 2 3
1 2 5
2 1 3
output
9
15
7
13

一开始样例有点小错,但上面这个是对的。

无非就是加个线段树操作,只不过平时线段树上就储存一个数,这边是储存一个数组,因为这数组32大小,也可以理解为创建了32个线段树。

修改操作:将原来数二进制中有1的位置减一,将新的数中二进制中有1的位置加一

求和操作:算出l到r之间二进制情况,用一个辅助函数b[32]储存即可

计算:和上一题一样。

#include 
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
#define ls		i<<1
#define rs		i<<1|1
const ll mod=1e9+7;
const int maxn=1e5+7;
ll n,m,x,y,a[maxn][32],k,b[32];
ll sum[maxn<<2][32];
ll read(){
     
    ll x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
     
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
     
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
    return x*f;
}
ll qpow(ll a, int b){
     
	ll ans=1;
	while(b){
     
		if(b&1) ans=(ans*a)%mod;
		a=(a*a)%mod;
		b>>=1;
	}
	return ans;
}

void pushup(int i){
     	for(int j=0;j<32;j++)	sum[i][j]=sum[ls][j]+sum[rs][j];	}
void build(int l,int r,int i){
     
	if(l==r){
     
		for(int j=0;j<32;j++)	sum[i][j]=a[l][j];
		return;
	}
	int m=(l+r)>>1;
	build(l,m,ls);
	build(m+1,r,rs);
	pushup(i);
}
void update(int l,int r,int i,int x,int k,int c){
     
	if(l==r){
     
		sum[i][k]+=c;
		return;
	}
	int m=(l+r)>>1;
	if(x<=m)	update(l,m,ls,x,k,c);
	else		update(m+1,r,rs,x,k,c);
	pushup(i);
}

void query(int l,int r,int i,int x,int y){
     
	if(x<=l&&y>=r){
     
		for(int j=0;j<32;j++)	b[j]+=sum[i][j];
		return;
	}
	int m=(l+r)>>1;
	if(x<=m)	query(l,m,ls,x,y);
	if(y>m)		query(m+1,r,rs,x,y);
}
void ccc(){
     
	ll ans=0;
	for(int i=0;i<32;i++){
     
		if(!b[i])	continue;
		ans=(ans+((qpow(2,i)*(qpow(2,b[i])-1)))%mod)%mod;
	}
	printf("%lld\n",ans);
}
int main(){
     
	n=read();	m=read();
	for(int i=1;i<=n;i++){
     
		x=read();	y=0;
		while(x){
     
			a[i][y]=x&1;
			x>>=1;
			y++;
		}
	}
	build(1,n,1);
	while(m--){
     
		k=read();	x=read();	y=read();
		if(k==1){
     
			k=0;
			while(y){
     
				if((y&1)&&!a[x][k])	update(1,n,1,x,k,1);
				if(!(y&1)&&a[x][k])	update(1,n,1,x,k,-1);
				a[x][k]=y&1;
				y>>=1;
				k++;
			}
			for(int i=k;i<32;i++){
     
				if(a[x][i]){
     
					update(1,n,1,x,i,-1);
					a[x][i]=0;
				}
			}
		}
		else{
     
			memset(b,0,sizeof(b));
			query(1,n,1,x,y);
//			for(int i=0;i<32;i++)	cout<
//			cout<
			ccc();
		}
	}
	
}

引申

给你一个集合,求所有子集异或和之和。

答案是将所有数或起来,然后乘上2^(n-1)就是答案了。

自己思考吧,不难

你可能感兴趣的:(数论,算法)