红球进黑洞(线段树区间异或+区间求和)

题目链接:

红球进黑洞

 

题意:

给定一个长度为 n 的序列,有 m 次操作:

操作有2种:

1. 区间求和,即输入l,r,输出\sum_{i=l}^{r}a[i] .

2. 区间异或,即输入l,r,k,对于l\leq i\leq r,将 a[i] 变为 a[i]\oplus k .

数据范围:

1\leq n,m\leq 10^{5}0\leq a[i],k\leq 10^{5}

 

思路:

我们无法得到一个区间更新的公式来做到区间异或,但异或是2进制上的操作,我们可以想到对一个数二进制的每一位来进行操作。因为 0\leq a[i],k\leq 10^{5} ,所以二进制位数<20位,因此我们可以开20颗线段树,第 i 颗线段树来维护二进制第 i 位上 1 的个数。

由于0异或上任何数 = 任何数本身,所以如果要异或的 k 的二进制第 i 位上为 0,那么这位就不需要考虑了。

此外,1异或上0=1,1异或上1=0 。也就是说,异或 1 能把 0 变成 1,把 1 变成 0 。所以如果要异或的 k 的二进制第 i 位上为 1,区间更新的时候区间[l,r]中更新后 1 的个数就等以区间长度(r-l+1)减去更新前 1 的个数。(也就是更新后 1 的个数 = 更新前 0 的个数)那么现在区间更新的公式已经得到了。

最后查询的时候,区间[l,r]求和就等于区间[l,r]每一位上 1 的个数乘以当前二进制位所代表的数值。

 

Code:

#include 
using namespace std;

typedef long long ll;

const int MAX = 1e5+10;

typedef struct{
    ll sum,lazy;
}Point;

int n,m;
ll a[MAX];
Point tree[25][MAX<<2];

void PushUp(int id,int root)
{
    tree[id][root].sum=tree[id][root<<1].sum+tree[id][root<<1|1].sum;
}

void PushDown(int id,int root,int l,int r)
{
    if(tree[id][root].lazy==1){
        tree[id][root].lazy=0;
        tree[id][root<<1].lazy^=1;
        tree[id][root<<1|1].lazy^=1;
        int mid = (l+r)>>1;
        tree[id][root<<1].sum=(mid-l+1)-tree[id][root<<1].sum;
        tree[id][root<<1|1].sum=(r-mid)-tree[id][root<<1|1].sum;
    }
}

void build(int id,int l,int r,int root)
{
    if(l==r){
        tree[id][root].sum=((a[l]>>id)&1);
        tree[id][root].lazy=0;
        return;
    }
    int mid = (l+r)>>1;
    build(id,l,mid,root<<1);
    build(id,mid+1,r,root<<1|1);
    PushUp(id,root);
    return;
}

void update(int id,int L,int R,int l,int r,int root)
{
    if(L<=l&&r<=R){
        tree[id][root].sum=(r-l+1)-tree[id][root].sum;
        tree[id][root].lazy^=1;
        return;
    }
    int mid = (l+r)>>1;
    PushDown(id,root,l,r);
    if(L<=mid)  update(id,L,R,l,mid,root<<1);
    if(R>mid)   update(id,L,R,mid+1,r,root<<1|1);
    PushUp(id,root);
}

ll query(int id,int L,int R,int l,int r,int root)
{
    if(L<=l&&r<=R){
        return  tree[id][root].sum;
    }
    int mid = (l+r)>>1;
    PushDown(id,root,l,r);
    ll ans=0;
    if(L<=mid)  ans+=query(id,L,R,l,mid,root<<1);
    if(R>mid)   ans+=query(id,L,R,mid+1,r,root<<1|1);
    return ans;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    for(int i=0;i<=20;i++){
        build(i,1,n,1);
    }
    while(m--)
    {
        ll op,l,r,k;
        scanf("%lld",&op);
        if(op==1){
            scanf("%lld%lld",&l,&r);
            ll ans=0;
            for(int i=0;i<=20;i++){
                ans+=1ll*query(i,l,r,1,n,1)*(1ll<>i)&1){
                    update(i,l,r,1,n,1);
                }
            }
        }
    }
    return 0;
}

 

你可能感兴趣的:(ACM-线段树)