CF242E XOR on Segment(线段树加二进制拆分)

洛谷题目链接 https://www.luogu.com.cn/problem/CF242E

题目大意 :给你一个长度为n的序列a,有两种操作

1 对[l,r]区间求和;

2 对[l,r]区间的每一个数异或上x;

解析 :标准的线段树区间求和以及区间修改,首先异或的一个性质,你对一个区间异或上n个数,无论先后顺序,最后结果都一样,因此很容易想到直接用一个懒标记来存要异或的数字,不到迫不得已不下推。问题的关键在于,懒标记如何下推,以及区间如何更新,如果你单纯那数字来进行操作,显然行不通,因为a^c+b^c并不等于(a+b)^c,不满足结合性,所以你没办法直接对一个大区间修改,但又要求和。所以这里要用到二进制拆分。

二进制拆分就是把每一个数字,用二进制来表示,用一个数组 f[k][i]来表示线段树下标为K的区间,这个区间的和的二进制第i位是几,注意这里的二进制是不进位的,比如1号区间是数字4,二进制拆完后是0100,而2号区间是数字14,二进制是1110,则他们的父亲区间的二进制表示为1210,直接按位加就行,1210,转成10进制就是1*2的3次方+2*2的2次方+1*2的1次方等于18。这样每个区间的和就已经表示完了。这样表示过后就能进行区间修改操作了。

异或操作,相同为0,不同为1,对于每次要异或操作的数字x,它的每一位二进制位如果是0,0^0=0, 0^1=1, 相当于对我们没有进行修改操作,我们就不看,如果是1,1^0=1, 1^1=0, 相当宇对我们的二进制位进行了一个取反操作,那就很简单了,我们对于要异或的数字的每一位1,进行对应位置修改就行。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,mod=998244353;
int f[400005][33];//f[k][i]代表线段树下标为K的区间里,这个数字二进制第i位是多少
ll lazy[400005];//异或的懒标记
ll c[35];
ll a[100005];
void build(int l,int r,int k)
{
    lazy[k]=0;
    if(l==r)
    {
       ll t=a[l];
       for(int i=0;i<33;i++)
       {
           if((t>>i)&1)f[k][i]=1;//把每个数字拆成二进制
       }
       return ;
    }
    int mid=(l+r)/2;
    build(l,mid,k*2);
    build(mid+1,r,k*2+1);
    for(int i=0;i<33;i++)
    {
       f[k][i]=f[k*2][i]+f[k*2+1][i];//合并左儿子和右儿子的二进制,不进位加法
    }
}
void pushdown(int l,int r,int k)
{
    if(lazy[k])
    {
        int mid=(l+r)/2;
        for(int i=0;i<33;i++)
        {
            if((lazy[k]>>i)&1)
            {
                f[k*2][i]=(mid-l+1)-f[k*2][i];//懒标记下推左右儿子
                f[k*2+1][i]=(r-mid)-f[k*2+1][i];
            }
        }
        lazy[k*2]^=lazy[k];
        lazy[k*2+1]^=lazy[k];
        lazy[k]=0;
    }
}
void update(int l1,int r1,int l,int r,int k,ll x)
{
    if(l>r1||r<l1)
    {
        return;
    }
    if(l1<=l&&r<=r1)
    {
       lazy[k]^=x;
       for(int i=0;i<33;i++)//对要异或的数字二进制拆分
       {
           if((x>>i)&1)
           {
               f[k][i]=(r-l+1)-f[k][i];//当异或的这个数的二进制位为1时,要进行更新
           }
       }
       return;
    }
    pushdown(l,r,k);
    int mid=(l+r)/2;
    update(l1,r1,l,mid,k*2,x);
    update(l1,r1,mid+1,r,k*2+1,x);
    for(int i=0;i<33;i++)
    {
       f[k][i]=f[k*2][i]+f[k*2+1][i];
    }

}
ll query(int l1,int r1,int l,int r,int k)
{
         if(l>r1||r<l1)
         {
             return 0;
         }
         if(l1<=l&&r<=r1)
         {
             ll ans=0;
             for(int i=0;i<33;i++)
             {
                 ans+=c[i]*f[k][i];//求值
             }
             return ans;
         }
         pushdown(l,r,k);
         int mid=(l+r)/2;
         return query(l1,r1,l,mid,k*2)+query(l1,r1,mid+1,r,k*2+1);
}
int main()
{
   c[0]=1;
   for(int i=1;i<=32;i++)
   {
       c[i]=c[i-1]*2;
   }
   cin>>n;
   for(int i=1;i<=n;i++)cin>>a[i];
   build(1,n,1);
   int q,op,l,r;
   ll x;
   cin>>q;
   while(q--)
   {
       cin>>op;
       if(op==1)
       {
           cin>>l>>r;
           ll ans=query(l,r,1,n,1);
           cout<<ans<<endl;
       }
       else
       {
           cin>>l>>r>>x;
           update(l,r,1,n,1,x);
       }

   }

}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值