• BZOJ 2568 比特集合


    题目链接:http://www.lydsy.com:808/JudgeOnline/problem.php?id=2568

    题意:维护一个集合S,支持以下操作:

    (1)INS M : 将元素 M 插入到集合S中;
    (2)DEL M : 将集合S中所有等于 M 的元素删除;
    (3)ADD M : 将集合S中的所有元素都增加数值M ;
    (4)QBIT k : 查询集合中有多少个元素满足其二进制的第 k位为 1 。

    思路:

    (1) ADD 操作的那个和单独拿出来,设为sum,集合S中的每个元素x实际值为x+sum;

    (2)设f[k][t]表示第k位为1,且这个数字小于等于t的数字的个数。每次查询时,设L=2^x,R=2^(x+1)-1,则答案为f[k][R]-f[k][L-1];

    (3)由于增加一个数字时这个f值是成段改变,因此要用树状数组维护这个f数组;

    (4)对于那些插入的数字都是多少以及每个数字有多少个,用一个map记录,这样删除时就知道在树状数组要减去多少。

    我当时有个问题没有明白,因为插入x时实际要插入的数字是x-sum,那么x-sum为负数时这个位跟正数的位不太一样。负数的二进制表示是对应正数的二进制表示取反加1。

    比如

    -1=11111111 11111111  11111111 11111111 

    -2=11111111 11111111  11111111 11111110

    -3=11111111 11111111  11111111 11111101

    -4=11111111 11111111  11111111 11111100

    直接插入(下面可以看到,不需要特殊考虑负数)

    后面那还加了个1是因为树状数组里下标都是从1开始的。

    然后求和时是这样的

    这个分为两部分,第一部分:计算的是[L,R]区间,设k=2,那么二进制表示L=100,R=111。设sum=1011,那么实际要计算的区间为[001,100],只要一个数字的后三位在这个区间,即[001,100],那么它加上sum之后的后三位都会落到[L,R]区间。其实这个是没有进位的。

    我们再设sum=1110,其他不变,那么上面的实际求和区间变成[000,001]。我们发现,除了这个区间,[110,111]这个区间也是可以的。这个其实是进位产生的,进位之后求和区间由[100,111]变为[1100,1111],这样减去sum的后三位110实际区间为[110,1001],我们发现1001,1000都不会有这个值,所以实际就是[110,111]。这就是上面求和的第二部分。

    那么一个负数加上sum之后也可能到达这个区间,sum=1110,[-10,-7],这些负数的二进制为

    -10=11111111 11111111  11111111 11110110 

    -9  =11111111 11111111  11111111 11110111

    -8  =11111111 11111111  11111111 11111000

    -7  =11111111 11111111  11111111 11111001

    我们发现,后三位都在计算的两个区间里。所以负数不需要额外考虑。

    int S[20][N];
    map<int,int> mp;
     
    int n;
     
    void add(int k,int x,int t)
    {
        while(x<N) S[k][x]+=t,x+=x&-x;
    }
     
     
    int get(int k,int x)
    {
        int ans=0;
        while(x) ans+=S[k][x],x-=x&-x;
        return ans;
    }
     
    int main()
    {
     
        n=myInt();
     
        int sum=0;
        while(n--)
        {
            char op[10];
            int x;
            scanf("%s%d",op,&x);
            if('A'==op[0]) sum+=x;
            else if('I'==op[0])
            {
                x-=sum;
                mp[x]++;
                for(int i=0;i<16;i++) add(i,(x&((1<<(i+1))-1))+1,1);
            }
            else if('D'==op[0])
            {
                x-=sum;
                int t=mp[x];
                mp[x]=0;
                for(int i=0;i<16;i++) add(i,(x&((1<<(i+1))-1))+1,-t);
            }
            else if('Q'==op[0])
            {
                int ans=0;
                int L=1<<x,R=(1<<(x+1))-1;
                ans+=get(x,min(1<<16,max(0,R-(sum&((1<<(x+1))-1))+1)));
                ans-=get(x,min(1<<16,max(0,L-(sum&((1<<(x+1))-1)))));
                L|=1<<(x+1);
                R|=1<<(x+1);
                ans+=get(x,min(1<<16,max(0,R-(sum&((1<<(x+1))-1))+1)));
                ans-=get(x,min(1<<16,max(0,L-(sum&((1<<(x+1))-1)))));
                printf("%d
    ",ans);
            }
        }
    }
    

      

  • 相关阅读:
    DShow实现一个avi视频的播放(含有个人解释和注释)
    指针和引用的区别
    从头看算法导论 习题2.3-7 深入分析
    程序员福利各大平台免费接口,非常适用
    NYOJ 58 最少步数
    NYOJ 42 一笔画问题
    NYOJ 1058 部分和问题
    NYOJ 21 三个水杯
    winform里面网页显示指定内容
    C#中List<T>对象的深度拷贝问题
  • 原文地址:https://www.cnblogs.com/jianglangcaijin/p/4221911.html
Copyright © 2020-2023  润新知