bzoj 4942 NOI2017 整数 (压位+线段树)

本文介绍了一道NOI级别的题目,该题需要维护一个大整数并支持特定的加减操作及位查询。文章详细解释了利用线段树进行位运算优化的方法,并分享了实现过程中的技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:让你维护一个数x(x位数<=3*1e7),要支持加/减a*2^b,以及查询x的第i位在二进制下是0还是1

作为一道noi的题,非常考验写代码综合能力,敲+调+借鉴神犇的代码 3个多小时才过...

思路并不难,题目里b<=30n暗示压位,每次压30位可过

先分析一下加法,加a*2^b相当于在第b-1位加a,如果进位了(即>=2^{^{30}}-1),那就在下一位+1,如果下一位还进位了,那就再下一位+1......

暴力进位显然不可取,那么用线段树维护它,维护连续的几大位之内是否全都是1

a<=1e9<=2^30,所以它最多被拆成两大位压进线段树里

那么每一个大位的加法操作可以变成 (方便描述,下面的数字是从左往右读):

1.当前位+=val,拆分出的val<=2^30

2.如果进位了,就去线段树里找接下来连续是1的最大的位置,然后在它下一位+1 比如01011 11111 10110,就是在第三位+1(实际操作可以直接找它下一位)

3.把这两位之间(不包括两端)的所有的大位都改成2^{^{30}}-1,区间修改打标记

减法操作和加法非常类似,借位就是找最右边是

查询操作非常简单没什么好说的,别忘了先下推标记再查询...

最先我的代码写了1个多小时,维护的东西一大堆,感觉恶心得一匹而且巨难调

实在是没想到好的优化方法,就借鉴了神犇gxzlegend的一些神级操作(orzorz):

a组(any)维护它子节点的&值,e数组(exist)维护它子节点的|值,这个维护方法很神,比我原来想的简单得多,而且不需要乱七八糟的打标记

find_inf和find_zero这两个函数也非常简洁明了

以及感谢LOJ的数据顺便吐槽一句辣鸡bzoj

#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long 
#define inf (1<<30)-1
#define il inline
#define ls rt<<1
#define rs rt<<1|1
#define N 1000100
using namespace std;
//re
int n,ma,t1,t2,t3;
int a[N<<2],e[N<<2],tag[N<<2];

int gc()
{
    int rett=0,fh=1;char p=getchar();
    while(p<'0'||p>'9'){if(p=='-')fh=-1;p=getchar();}
    while(p>='0'&&p<='9'){rett=(rett<<3)+(rett<<1)+p-'0';p=getchar();}
    return rett*fh;
}
il void pushup(int rt){
    e[rt]=e[rt<<1]|e[rt<<1|1];
    a[rt]=a[rt<<1]&a[rt<<1|1];
}
il void pushdown(int rt){
    if(tag[rt]==-1) a[ls]=a[rs]=e[ls]=e[rs]=0,tag[rt]=0,tag[ls]=tag[rs]=-1;
    if(tag[rt]==1) a[ls]=a[rs]=e[ls]=e[rs]=inf,tag[rt]=0,tag[ls]=tag[rs]=1;
}
int find_inf(int x,int l,int r,int rt)
{
    if(a[rt]==inf) return -1;
    if(l==r) return l;
    pushdown(rt);
    int mid=(l+r)>>1;
    if(x<=mid){
        int pos=find_inf(x,l,mid,rt<<1);
        if(pos==-1) return find_inf(x,mid+1,r,rt<<1|1);
        else return pos;
    }
    return find_inf(x,mid+1,r,rt<<1|1);
}
int find_zero(int x,int l,int r,int rt)
{
    if(!e[rt]) return -1;
    if(l==r) return l;
    pushdown(rt);
    int mid=(l+r)>>1;
    if(x<=mid){
        int pos=find_zero(x,l,mid,rt<<1);
        if(pos==-1) return find_zero(x,mid+1,r,rt<<1|1);
        else return pos;
    }
    return find_zero(x,mid+1,r,rt<<1|1);
}
int upd1(int x,int l,int r,int rt,int p)
{
    if(l==r)
    {
        a[rt]+=p,e[rt]+=p;
        if(a[rt]>inf){a[rt]-=(inf+1);e[rt]-=(inf+1);return 1;}
        if(a[rt]<0){a[rt]+=(inf+1);e[rt]+=(inf+1);return -1;}
        return 0;
    }
    pushdown(rt);
    int mid=(l+r)>>1,ans=0;
    if(x<=mid) ans=upd1(x,l,mid,rt<<1,p);
    else ans=upd1(x,mid+1,r,rt<<1|1,p);
    pushup(rt);return ans;
}
void upd2(int L,int R,int l,int r,int rt,int val)
{
    if(L<=l&&r<=R){
        tag[rt]=(val)?1:-1;
        a[rt]=e[rt]=(val)?inf:0;
        return;}
    pushdown(rt);
    int mid=(l+r)>>1;
    if(L<=mid) upd2(L,R,l,mid,rt<<1,val);
    if(R>mid) upd2(L,R,mid+1,r,rt<<1|1,val);
    pushup(rt);
}
void add(int x,int y)
{
    int p=upd1(x,0,n,1,y);
    if(!p) return;
    int pos=find_inf(x+1,0,n,1);
    if(pos-1>=x+1) upd2(x+1,pos-1,0,n,1,0);
    upd1(pos,0,n,1,1);
}
void del(int x,int y)
{
    int p=upd1(x,0,n,1,-y);
    if(!p) return;
    int pos=find_zero(x+1,0,n,1);
    if(pos-1>=x+1) upd2(x+1,pos-1,0,n,1,1);
    upd1(pos,0,n,1,-1);
}
int query(int x,int l,int r,int rt,int p)
{
    if(l==r) return ((1<<p)&a[rt])?1:0;
    int mid=(l+r)>>1;
    pushdown(rt);
    if(x<=mid) return query(x,l,mid,rt<<1,p);
    else  return query(x,mid+1,r,rt<<1|1,p);
}
int main()
{
    scanf("%d%d%d%d",&n,&t1,&t2,&t3);
    int fl,x,y;
    for(int i=1;i<=n;i++)
    {
        fl=gc();
        if(fl==1){
            x=gc(),y=gc();
            if(x>=0){
                add(y/30,(x&((1<<(30-y%30))-1))<<(y%30));
                add(y/30+1,x>>(30-y%30));
            }else{ x=-x;
                del(y/30,(x&((1<<(30-y%30))-1))<<(y%30));
                del(y/30+1,x>>(30-y%30));
            }
        }else{
            x=gc();
            printf("%d\n",query(x/30,0,n,1,x%30));
        }
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值