FZU2105Digits Count--位运算线段树(|,&,^)两种解法

题目链接http://acm.fzu.edu.cn/problem.php?pid=2105
Time Limit: 10000 m Sec
Memory Limit : 262144 KB

Problem Description

Given N integers A={A[0],A[1],…,A[N-1]}. Here we have some operations:

Operation 1: AND opn L R

Here opn, L and R are integers.

For L≤i≤R, we do A[i]=A[i] AND opn (here “AND” is bitwise operation).

Operation 2: OR opn L R

Here opn, L and R are integers.

For L≤i≤R, we do A[i]=A[i] OR opn (here “OR” is bitwise operation).

Operation 3: XOR opn L R

Here opn, L and R are integers.

For L≤i≤R, we do A[i]=A[i] XOR opn (here “XOR” is bitwise operation).

Operation 4: SUM L R

We want to know the result of A[L]+A[L+1]+…+A[R].

Now can you solve this easy problem?

Input

The first line of the input contains an integer T, indicating the number of test cases. (T≤100)

Then T cases, for any case, the first line has two integers n and m (1≤n≤1,000,000, 1≤m≤100,000), indicating the number of elements in A and the number of operations.

Then one line follows n integers A[0], A[1], …, A[n-1] (0≤A[i]<16,0≤i<n).

Then m lines, each line must be one of the 4 operations above. (0≤opn≤15)

Output

For each test case and for each “SUM” operation, please output the result with a single line.

Sample Input

1
4 4
1 2 4 7
SUM 0 2
XOR 5 0 0
OR 6 0 3
SUM 0 2

Sample Output

7
18

Hint

A = [1 2 4 7]

SUM 0 2, result=1+2+4=7;

XOR 5 0 0, A=[4 2 4 7];

OR 6 0 3, A=[6 6 6 7];

SUM 0 2, result=6+6+6=18.


题目大意:给你n个元素,m次操作:
操作1:SUM l r
操作2:OR val l r
操作3:XOR val l r
操作4:AND val l r
每次对SUM做出回答。

第一种做法比较暴力,耗费的时间也会比较久,题目的数据范围非常小,只有16,也就是说会有大量重复的元素,那么我们可以直接暴力更新,对于线段树分父节点,如果它的左右儿子一样,我们就将它的值pushdown为儿子值,然后直接位运算,因为现在的父节点的区间范围的数都是一样了,那么这些数的和就是父节点的区间大小乘上父节点的值了:

void update(int l,int r,int rt,int mark,int L,int R,int val)
{
    if (l>=L && r<=R && tree[rt]!=-1){//注意!!!
        if (mark==1) tree[rt]^=val;
        else if (mark==2) tree[rt]&=val;
        else tree[rt]|=val;
        return;
    }
    int mid=(l+r)>>1;
    if (tree[rt]!=-1){
        tree[ls]=tree[rs]=tree[rt];tree[rt]=-1;
    }
    if (mid>=L) update(lson,mark,L,R,val);
    if (mid<R) update(rson,mark,L,R,val);
    pushup(rt);
    //pushup:if (tree[ls]!=-1 && tree[ls]==tree[rs]) 
    //				tree[rt]=tree[ls]
}

所以如果数据量一大,这么写就会T,因为如果每个数都不一样的话它会单个更新每个叶子节点。。。。复杂度爆表

接下来的就是比较优秀的写法了,我们将每个数拆成二进制的形式,每个节点有一个小数组用来记录二进制状态下每一位是0还是1。然后用两个懒标记维护分别记为set和setxor,set为区间置数,维护|(或)及&(与)两个运算的,我们可以发现,当|1的时候不管值是多少,都会是1,那么也就是说我们直接将整个区间置数位1,|0的时候没有变化,当&0的时候,所有的数都是0,我们将区间置数位0,那么也就是说&和|都是可以通过区间置数来完成的。

维护异或的时候就是区间取反了,对区间的每个数取反,那么只有当^1的时候才会有效果。这里就直接放出update的代码,这个是核心,理解了的话pushdown也会很容易理解:(注意拆位!)

void update(int l,int r,int rt,int L,int R,int pos,int val)
{//pos代表二进制下第pos位
    if (l>=L && r<=R){
        if (val==-1) {
            tree[rt].sum[pos]=(r-l+1)-tree[rt].sum[pos];//区间每个数的第pos位取反,0->1更新为区间长度-以前的1的个数
            if (tree[rt].set[pos]!=-1)
                tree[rt].set[pos]^=1;//如果存在区间置数的话,这个区间的值都是set[pos],那么我们直接对标记取反就好了
            else tree[rt].setxor[pos]^=1;
        }
        else {
            tree[rt].sum[pos]=(r-l+1)*val;//&0的话区间全部置为0,|1的话全部置为1,否则不变
            tree[rt].set[pos]=val;//区间置数
            tree[rt].setxor[pos]=0;//由于区间置数的存在,取反没有意义了,无论如何值都会是val
        }
        return;
    }
    pushdown(l,r,rt,pos);
    int mid=(l+r)>>1;
    if (mid>=L) update(lson,L,R,pos,val);
    if (mid<R) update(rson,L,R,pos,val);
    pushup(rt,pos);
}

****

void pushdown(int l,int r,int rt,int pos)
{
    int mid=(l+r)>>1;
    if (tree[rt].set[pos]!=-1){//区间置数0,1标记传下
        tree[ls].set[pos]=tree[rs].set[pos]=tree[rt].set[pos];
        tree[ls].sum[pos]=(mid-l+1)*tree[rt].set[pos];
        tree[rs].sum[pos]=(r-mid)*tree[rt].set[pos];
        tree[rt].set[pos]=-1;
        tree[rt].setxor[pos]=tree[ls].setxor[pos]=tree[rs].setxor[pos]=0;
    }
    else if (tree[rt].setxor[pos]){
        tree[ls].sum[pos]=(mid-l+1)-tree[ls].sum[pos];
        tree[rs].sum[pos]=(r-mid)-tree[rs].sum[pos];
        if (tree[ls].set[pos]!=-1)
            tree[ls].set[pos]^=1;
        else tree[ls].setxor[pos]^=1;
        if (tree[rs].set[pos]!=-1)
            tree[rs].set[pos]^=1;
        else tree[rs].setxor[pos]^=1;
        tree[rt].setxor[pos]=0;
    }
}

以下是暴力AC代码:

#include <cstdio>
#include <algorithm>
using namespace std;

#define ll long long
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define ls rt<<1
#define rs rt<<1|1

const int mac=1e6+10;

int tree[mac<<2];

void pushdown(int rt)
{
    if (tree[ls]!=-1 && tree[ls]==tree[rs])
        tree[rt]=tree[ls];
}

void build(int l,int r,int rt)
{
    tree[rt]=-1;
    if (l==r){
        scanf ("%d",&tree[rt]);
        return;
    }
    int mid=(l+r)>>1;
    build(lson);build(rson);
    pushdown(rt);
}

void update(int l,int r,int rt,int mark,int L,int R,int val)
{
    if (l>=L && r<=R && tree[rt]!=-1){
        if (mark==1) tree[rt]^=val;
        else if (mark==2) tree[rt]&=val;
        else tree[rt]|=val;
        return;
    }
    int mid=(l+r)>>1;
    if (tree[rt]!=-1){
        tree[ls]=tree[rs]=tree[rt];tree[rt]=-1;
    }
    if (mid>=L) update(lson,mark,L,R,val);
    if (mid<R) update(rson,mark,L,R,val);
    pushdown(rt);
}

int query(int l,int r,int rt,int L,int R)
{
    int ans=0;
    if (l>=L && r<=R && tree[rt]!=-1){
        return tree[rt]*(r-l+1);
    }
    int mid=(l+r)>>1;
    if (tree[rt]!=-1){
        tree[ls]=tree[rs]=tree[rt];tree[rt]=-1;
    }
    if (mid>=L) ans+=query(lson,L,R);
    if (mid<R) ans+=query(rson,L,R);
    return ans;
}

int main()
{
    int t;
    scanf ("%d",&t);
    while (t--){
        int n,m;
        scanf ("%d%d",&n,&m);
        build(1,n,1);
        char s[5];
        for (int i=1; i<=m; i++){
            scanf ("%s",s);
            //printf ("++%c++",s[0]);
            int l,r,val;
            if (s[0]=='S'){
                scanf ("%d%d",&l,&r);
                l++;r++;
                //printf ("%d %d",l,r);
                int ans=query(1,n,1,l,r);
                printf ("%d\n",ans);
            }
            else {
                scanf ("%d%d%d",&val,&l,&r);
                l++;r++;
                if (s[0]=='X')
                    update(1,n,1,1,l,r,val);
                else if (s[0]=='A')
                    update(1,n,1,2,l,r,val);
                else 
                    update(1,n,1,3,l,r,val);
            }
        }
    }
    return 0;
}

以下是比较正解的AC代码:

#include <cstdio>
#include <algorithm>
using namespace std;

#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define ls rt<<1
#define rs rt<<1|1
#define ll long long

const int mac=1e6+10;
struct node
{
    int sum[4],set[4],setxor[4];
}tree[mac<<2];
int a[mac];

void pushdown(int l,int r,int rt,int pos)
{
    int mid=(l+r)>>1;
    if (tree[rt].set[pos]!=-1){//区间置数0,1标记传下
        tree[ls].set[pos]=tree[rs].set[pos]=tree[rt].set[pos];
        tree[ls].sum[pos]=(mid-l+1)*tree[rt].set[pos];
        tree[rs].sum[pos]=(r-mid)*tree[rt].set[pos];
        tree[rt].set[pos]=-1;
        tree[rt].setxor[pos]=tree[ls].setxor[pos]=tree[rs].setxor[pos]=0;
    }
    else if (tree[rt].setxor[pos]){
        tree[ls].sum[pos]=(mid-l+1)-tree[ls].sum[pos];
        tree[rs].sum[pos]=(r-mid)-tree[rs].sum[pos];
        if (tree[ls].set[pos]!=-1)
            tree[ls].set[pos]^=1;
        else tree[ls].setxor[pos]^=1;
        if (tree[rs].set[pos]!=-1)
            tree[rs].set[pos]^=1;
        else tree[rs].setxor[pos]^=1;
        tree[rt].setxor[pos]=0;
    }
}

void pushup(int rt,int pos)
{
    tree[rt].sum[pos]=tree[ls].sum[pos]+tree[rs].sum[pos];
}

void build(int l,int r,int rt)
{
    for (int i=0; i<4; i++){
        tree[rt].set[i]=-1;
        tree[rt].setxor[i]=0;//异或0的话是不变的
        tree[rt].sum[i]=0;
    }
    if (l==r){
        for (int i=0; i<4; i++)
            if (a[l]&(1<<i)) 
                tree[rt].sum[i]++;
        return;
    }
    int mid=(l+r)>>1;
    build(lson);build(rson);
    for (int i=0; i<4; i++)
        pushup(rt,i);
}

void update(int l,int r,int rt,int L,int R,int pos,int val)
{
    if (l>=L && r<=R){
        if (val==-1) {
            tree[rt].sum[pos]=(r-l+1)-tree[rt].sum[pos];//区间每个数的第pos位取反,0->1更新为区间长度-以前的1的个数
            if (tree[rt].set[pos]!=-1)
                tree[rt].set[pos]^=1;//如果存在区间置数的话,这个区间的值都是set[pos],那么我们直接对标记取反就好了
            else tree[rt].setxor[pos]^=1;
        }
        else {
            tree[rt].sum[pos]=(r-l+1)*val;//&0的话区间全部置为0,|1的话全部置为1,否则不变
            tree[rt].set[pos]=val;//区间置数
            tree[rt].setxor[pos]=0;//由于区间置数的存在,取反没有意义了,无论如何值都会是val
        }
        return;
    }
    pushdown(l,r,rt,pos);
    int mid=(l+r)>>1;
    if (mid>=L) update(lson,L,R,pos,val);
    if (mid<R) update(rson,L,R,pos,val);
    pushup(rt,pos);
}

int query(int l,int r,int rt,int L,int R,int pos)
{
    int ans=0;
    if (l>=L && r<=R) return tree[rt].sum[pos];
    int mid=(l+r)>>1;
    pushdown(l,r,rt,pos);
    if (mid>=L) ans+=query(lson,L,R,pos);
    if (mid<R) ans+=query(rson,L,R,pos);
    return ans;
}

int main()
{
   // freopen("in.txt","r",stdin); 
	int t;
    scanf ("%d",&t);
    while (t--){
        int n,m;
        scanf ("%d%d",&n,&m);
        for (int i=1; i<=n; i++)
            scanf ("%d",&a[i]);
        build(1,n,1);
        for (int i=1; i<=m; i++){
            char s[5];
            scanf ("%s",s);
            int l,r,val;
            if (s[0]=='S'){
                scanf ("%d%d",&l,&r);
                l++;r++;
                int ans=0;
                for (int j=0; j<4; j++)
                    ans+=query(1,n,1,l,r,j)*(1<<j);
                printf ("%d\n",ans);
            }
            else if (s[0]=='O'){//or 或
                scanf ("%d%d%d",&val,&l,&r);
                l++;r++;
                for (int j=0; j<4; j++)
                    if (val&(1<<j))
                        update(1,n,1,l,r,j,1);
            }
            else if (s[0]=='X'){//xor 异或
                scanf ("%d%d%d",&val,&l,&r);
                l++;r++;
                for (int j=0; j<4; j++)
                    if (val&(1<<j))
                        update(1,n,1,l,r,j,-1);
            }
            else {//and 与
                scanf ("%d%d%d",&val,&l,&r);
                l++;r++;
                for (int j=0; j<4; j++)
                    if (!(val&(1<<j)))
                        update(1,n,1,l,r,j,0);
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值