【挖坑记】JZOJ 4711 Binary

题目大意

       给定一个长度为n的整数数列a和q次操作:
       修改操作:形如 1 x y,表示将 ax 的值修改为y;
       询问操作:形如 2 x y,表示询问 n1(ai+x) and y 的值。
      n,q<=105
      0<=ai,x,y<=220

【40%】n,q<=5000

       题目怎么说怎么做。

【另20%】所有询问的x=0

       二进制总共只有20位,直接记录每一位为1的有多少个数,假设记为cnt[i]查询时,y的第i个二进制位为1,就答案加上 2icnt[i]

【100%】n,q<=10^5

       我们还是对于每个二进制位单独考虑,且y这一位为1才考虑。
       不考虑+x时,我们还可以弄一棵值域线段树,那么cnt[i]所包含的数就是这样的:比i高的位任意,i位以内满足在 [2i1,2i1] 这个区间内。可以发现我们查询的区间对于整个值域来说并不连续,而是一段一段的,因此我们对每个二进制位都开一棵值域线段树,第i位的线段树存储的数则由 ai 变为 ai mod 2i 。这样我们操作第i位时,直接在第i位的线段树中查询 [2i1,2i1] 内的数有多少个,就行了。
       接下来考虑+x。这个实际上是对查询区间的位移,比如当前查询区间 [2i1,2i1] ,那么就变成 [2i1x,2i1x] 。要注意这里的x是 mod 2i 的。唯一的问题就是区间左端点可能为负。这好办,先把0到右端点的正常操作,假设左端点变成了 z ,那我们再查询 [2iz,2i1] 就行了。这里相当于是退位一样的东西。

代码

//我把线段树换成树状数组,这样常数小代码短
#include<cstdio>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

typedef long long LL;

const int maxn=(1e5)+5, MX=20, maxc=(1<<20)+5;

int n,a[maxn],er[MX+5];

int c[MX+2][maxc];
int lowbit(int x) {return x&(-x);}
void xg(int ty,int x,int z)
{
    if (x==0) {c[ty][0]+=z; return;}
    for(; x<er[ty]; x+=lowbit(x)) c[ty][x]+=z;
}
int get(int ty,int x)
{
    if (x<0) return 0;
    int re=0;
    for(; x; x-=lowbit(x)) re+=c[ty][x];
    return re+c[ty][0];
}

int q;
int main()
{
    fo(i,0,MX) er[i]=1<<i;

    scanf("%d %d",&n,&q);
    fo(i,1,n)
    {
        scanf("%d",&a[i]);
        fo(j,1,20) xg(j,a[i]%er[j],1);
    }

    while (q--)
    {
        int ty,x,y;
        scanf("%d %d %d",&ty,&x,&y);
        if (ty==1)
        {
            fo(i,1,20) xg(i,a[x]%er[i],-1);
            fo(i,1,20) xg(i,y%er[i],1);
            a[x]=y;
        } else
        {
            LL ans=0;
            fo(i,1,MX) if (y&er[i-1])
            {
                int xx=x%er[i], st=er[i-1]-xx, en=er[i]-1-xx;
                if (st<0)
                {
                    ans+=(LL)er[i-1]*get(i,en);
                    if (i>1) ans+=(LL)er[i-1]*(get(i,er[i]-1)-get(i,er[i]+st-1));
                } else
                {
                    ans+=(LL)er[i-1]*(get(i,en)-get(i,st-1));
                }
            }

            printf("%lld\n",ans);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值