Binary 【NOIP2016提高A组模拟8.17】

题目

这里写图片描述
样例输入:
这里写图片描述
6 6
8 9 1 13 9 3
1 4 5
2 6 9
1 3 7
2 7 7
1 6 1
2 11 13

样例输出:
这里写图片描述
45
19
21

数据范围:
这里写图片描述


剖解题目

被虐,不想说了了QAQ~~


思路

曾经做过一道类似的题,对于这种情况就是要把每个数拆成二进制去考虑,因为看到了每个数最大是 220 ,也就是最多只有20位,所以要往这方面想。


解法

40%:暴力。
60%:因为x=0,所以把每个数拆成二进制,并记录所有数里,每一位出现1的个数。修改时暴力 O(20) 修改。询问时,如果y二进制下第i位(右边数起,下限是1。下同)是1,那么对于答案的贡献就是 num[i]2i ,加入答案即可。
100%:继续上述思路,当x!=0时,我们应该如何统计答案?
我们知道,如果这个数a[i] and y后对答案有贡献,只有在二进制下两个数相同位都为1才行。那么对于所有数,我们用一个BIT来存储每一个位上的1的个数的情况。
treei,x 表示二进制下,前i位构成的十进制数为x的个数。
好像有点难理解?举个例子:当我们读入一个8时,8的二进制是1000,那么BIT内的变化就是这样:
tree[1][0]+1,tree[2][0]+1,tree[3][0]+1,tree[4][8]+1。好懂吧QwQ……
那么询问时,对于第k位,对答案的贡献就是(要考虑到加x):

Ans+=i=2k12k1(treek,ix2k1)

注意:
1.要开longlong。
2.树状数组无法访问0的情况,这时我们需要将树状数组第二维的下标整体右移一位。包括查询时的两个边界以及模数。
3.就是i-x时可能会变成负数,也就是越界.
一图来表示(很丑,自己画的,将就一下)
这里写图片描述
就是把左边界 l+2k ,然后图中求两块绿色部分的和,树状数组很容易解决。
至于为什么是对的,自己脑补脑补吧,感性理解一下。
4.segement tree会被卡常,如果觉得自己RP可以可以打- -|||。


代码

#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define ll long long

using namespace std;

const int maxx=22,maxn=1e5+5;
int n,q,a[maxn],tree[maxx][1<<maxx];
ll ans;

int lowbit(int x) 
{
    return x&-x;
}
void up(int i,int x,int kk)
{
    int nn=1<<i;
    while (x<=nn+1){
        tree[i][x]+=kk;
        x+=lowbit(x);
    }
}
void add(int x,int xx)
{
    int i=1,y=0;
    while (i<22){
        int k=x&1;
        if (k) y+=1<<i-1;
        up(i,y+1,xx);
        ++i;
        x>>=1;
    }
}
ll getsum(int x,int i)
{
    ll sum=0; 
    while (x>0){
        sum=sum+(ll)tree[i][x];
        x-=lowbit(x);
    }
    return sum;
}
void get(int x,int ii)
{
    int i=1;
    while (x){
        int k=x&1;
        if (k){
            int l=(1<<i-1)+1,r=(1<<i),nn=(1<<i);
            int ix=ii%nn;
            if (l-ix>0) ans+=(getsum(r-ix,i)-getsum(l-ix-1,i))*(1<<i-1);
            else {
                l=l-ix+nn;r=r-ix;
                ans+=(getsum(r,i)+getsum(nn,i)-getsum(l-1,i))*(1<<i-1);
            }
        }
        x>>=1;
        ++i;
    }
}
int main()
{
    scanf("%d%d",&n,&q);
    fo(i,1,n){
        scanf("%d",&a[i]);
        add(a[i],1);
    }
    fo(p,1,q){
        int t,x,y;
        scanf("%d%d%d",&t,&x,&y);
        if (t==1){
            add(a[x],-1);
            a[x]=y;
            add(a[x],1);
        }
        else {
            ans=0;
            get(y,x);
            printf("%lld\n",ans);
        }
    }
}

这里写图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值