2021-05-24异或线段树题1

D-子段异或(异或前缀和)

1.题意:

题目链接

2.题解:

详见参考博客
设a[i]为输入的数
b[i]为异或前缀和
b[i] = a[1] ^ a[2] ^ … ^ a[i - 1] ^ a[i]
已知一个数x^y = 0当且仅当x == y时成立
那么对于一段[1,r]来说,异或前缀和为b[r],那么如果想要以r为异或为0的子段的右半部分,那么只需要前面出现过一个数b[i] == b[r],那么[i + 1,r]这一段异或和为0
就比如
对于x1,x2,x3…xr,如果想要找到一段区间[i,r]使得其区间异或和为0,设x1,x2,x3…xj的异或和为b[j]那么如果b[j] == b[r],也就是说b[j] ^ b[j+1,r] = b[r]而只有x ^ y == x当且仅当y = 0时成立。
相当于一条直线上的点,每次加的cnt[i]都代表把i点前符合要求的点和第i个点连起来构成的线段个数
特判cnt【0】一般都等于1

3.ac代码:

应该能ac,进不去

#include <bits/stdc++.h>
#include <math.h>
using namespace std;
typedef long long ll;
const int N = 50000 + 10;
int a[N],x[N];
map<ll,int> mp;//因为数组下标无法达到2^30,所以用map 
int main(){
 	int n;
 	cin>>n;
 	mp[0]=1; //x[i]=0的特判 
 	for(int i=1;i<=n;i++){
 		cin>>a[i];
 		x[i]=x[i-1]^a[i];
	 }
	 int cnt=0;
	 for(int i=1;i<=n;i++){
	 	cnt+=mp[x[i]];
	 	mp[x[i]]++;
	 }
	 cout<<cnt<<endl;
	 

}

蓝桥杯姐妹题 前缀和-蓝桥杯省赛-K倍区间

#include <bits/stdc++.h>
#include <math.h>
using namespace std;
typedef long long ll;
const int N = 50000 + 10;
ll a[N],sum[N];
ll cnt[N];
int main(){
 	int n,k;
 	cin>>n>>k;
 	
 	for(int i=1;i<=n;i++){
 		cin>>a[i];
 		sum[i]+=a[i];
	 }
	 ll ans=0;
	 cnt[0]=1;//区间和为0的sum[0]已经有一个 
	 for(int i=1;i<=n;i++){
	 	ans+=cnt[sum[i]%k];
	 	cnt[sum[i]%k]++;
	 }
	 cout<<ans<<endl;
	 

}

CF242E XOR on Segment

1.题意:

1.对【l,r】区间每个值异或x
2.对区间求和

2.题解:

有用分块做的大佬,回头我再研究

将每个数字变成0/1二进制串,
每个线段树维护区间内第i位为1的个数
a XOR x 就是将a和x变成2进制,将a和x对应的每一位异或,变成了0/1异或,可以发现,
0/1 XOR 0 =0/1, 1/0 XOR 1=0/1 异或0不变,异或1相反
由于每个线段树维护的cnt[20],cnt【i】就是区间所有数字二进制时在第i位是1的个数,所以需要变的时候,只需要将这一位全取反,原来的为1的个数等于该区间0的个数,即区间长度-原来的1的个数
用懒标记,记录需要取反的次数,如果该位需要取反则当前区间该位变成相反的个数,同时懒标记个数++(父节点已经变化)
pushdown:将父标记下放到子标记上,同时父标记变为0,如果父标记是奇数,则改变原来子节点标记的奇偶性,子节点变化

3.ac代码:
#include <bits/stdc++.h>
#include <math.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int a[N];
const int INF=0x3f3f3f3f;
struct segment{
    int l,r;
    int cnt[22];//l~r区间中子段第i位上的1的个数
    int tag[22];
}tree[N<<2];
void pushup(int u){
    for(int i=0;i<20;i++)
        tree[u].cnt[i]=tree[u<<1].cnt[i]+tree[u<<1|1].cnt[i];
}
void pushdown(int u){
    for(int i=0;i<20;i++){
        int p=tree[u].tag[i];
        if(p){
            tree[u<<1].tag[i]+=p;
            tree[u<<1|1].tag[i]+=p;
            if(p&1){//如果p是奇数,会改变原来子树tag的奇偶性,所以要变
                tree[u<<1].cnt[i]=(tree[u<<1].r-tree[u<<1].l+1)-tree[u<<1].cnt[i];
                tree[u<<1|1].cnt[i]=(tree[u<<1|1].r-tree[u<<1|1].l+1)-tree[u<<1|1].cnt[i];
            }
            tree[u].tag[i]=0;

        }

    }
}
void build(int u,int l,int r){
    if(l==r){
        tree[u]={l,r};
        for(int i=0;i<20;i++){
            if(a[l]>>i&1) tree[u].cnt[i]=1;
        }
        return ;
    }
    tree[u]={l,r};
    int mid=tree[u].l+tree[u].r>>1;
    build(u<<1,l,mid);
    build(u<<1|1,mid+1,r);
    pushup(u);
}
void update(int u,int l,int r,int x){
    if(tree[u].l>=l&&tree[u].r<=r){
        for(int i=0;i<20;i++){
            if(x>>i&1){
                tree[u].cnt[i]=(tree[u].r-tree[u].l+1)-tree[u].cnt[i];
                tree[u].tag[i]++;
            }
        }
        return ;
    }
    int mid=tree[u].l+tree[u].r>>1;
    pushdown(u);
    if(l<=mid) update(u<<1,l,r,x);
    if(r>mid) update(u<<1|1,l,r,x);
    pushup(u);
}
ll query(int u,int l,int r){
    if(tree[u].l>=l&&tree[u].r<=r){
        ll ans=0,p=1;

        for(int i=0;i<20;i++){
            ans+=p*tree[u].cnt[i];
            p<<=1;
        }
        return ans;
    }
    ll sum=0;
    int mid=tree[u].l+tree[u].r>>1;
    pushdown(u);
    if(l<=mid) sum+=query(u<<1,l,r);
    if(r>mid) sum+=query(u<<1|1,l,r);
    return sum;
}
int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    build(1,1,n);
    int m;
    int p,l,r,x;
    cin>>m;
    for(int i=1;i<=m;i++){
        cin>>p>>l>>r;
        if(p==1){
            cout<<query(1,l,r)<<endl;
        }else{
            cin>>x;
            update(1,l,r,x);
        }
    }
}

异或和(位运算)

1.题意:

给一个n个元素的数组,求n个元素两两异或的和

2.题解:

参考
将所有元素都变为二进制串,显然和为某二进制位为1的个数二进制权值,在该位上为1的应该由该位上为0和为1异或得来的,所以该位上为0的数字的个数该位上为1的数字的个数,为所有异或后该位为1的情况,

3.ac代码:
#include <bits/stdc++.h>
#include <math.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int a[N];
int cnt[22];
int n;
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	
		for(int j=0;j<=20;j++){
			if(a[i]>>j&1) cnt[j]++;
		}
	}
	ll sum=0;
	for(int i=0;i<=20;i++){
		sum=sum+(ll)cnt[i]*(ll)(n-cnt[i])*(ll)(1<<i);
	}
	cout<<sum<<endl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值