【JZOJ5689】二进制

Description
Pupil 发现对于一个十进制数,无论怎么将其的数字重新排列,均不影响其是不是3 的倍数。他想研究对于二进制,是否也有类似的性质。于是他生成了一个长为n的二进制串,希望你对于这个二进制串的一个子区间,能求出其有多少位置不同的连续子串,满足在重新排列后(可包含前导 0)是一个3的倍数。两个位置不同的子区间指开始位置不同或结束位置不同。由于他想尝试尽量多的情况,他有时会修改串中的一个位置,并且会进行多次询问。

Input
输入第一行包含一个正整数 n,表示二进制数的长度。
之后一行 n 个空格隔开的整数,保证均是0或1,表示该二进制串。
之后一行一个整数 m,表示询问和修改的总次数。
之后m行每行为1 i,表示Pupil修改了串的第i个位置(0 变成 1 或 1 变成 0 ),或 2 l r,表示Pupil询问的子区间是[l,r]。
串的下标从 1 开始。

Output
对于每次询问,输出一行一个整数表示对应该询问的结果。

Sample Input

4
1 0 1 0
3
2 1 3
1 3
2 3 4

Sample Output

2
3

Data Constraint
对于 20% 的数据,1 ≤ n,m ≤ 100。
对于 50% 的数据,1 ≤ n,m ≤ 5000。
对于 100% 的数据,1 ≤ n,m ≤ 100000,l ≤ r。

Hint
对于第一个询问,区间 [2,2] 只有数字 0,是 3 的倍数,区间 [1,3] 可以重排成011(2) = 3(10) ,是3的倍数,其他区间均不能重排成 3 的倍数。对于第二个询问,全部三个区间均能重排成 3 的倍数(注意 00 也是合法的)。


Analysis

sb结论码题。。。
考虑对于一个区间重排不能凑成3的倍数的情况
1. 只有1个1
2. 1的个数为奇数且0的个数小于2

那不就很好维护了吗~~也就维护几个量
不合法的区间数
区间长度
左边前缀0长度
左边前缀1长度
左边前缀0之后第下一段连续0长度
左边前缀1之后第下一段连续1长度
右边。。。

也就10个→_→

#include<cstring>
#include<cstdio>
#define N 200100
#define ll long long

using namespace std;

int a[N],son[N][2],n,opl,opr,cnt,root,m;
ll sum[N];
ll calc1(int a,int b){return (ll)(a/2)*(ll)(b+1>>1)+(ll)(a+1>>1)*(ll)(b/2);} 
ll calc2(int a){if(!a)return 0;return (ll)(a/2)*(ll)(a/2-1)/2+(ll)(a+1>>1)*(ll)(a-1>>1)/2;}
ll calc3(int a){if(!a)return 0;return (ll)(a-1>>1);}
struct node{
    int l0,l1,r0,r1,l_0,l_1,r_0,r_1,v;ll sum;
    void clear(){
        v=1;l0=l1=r0=r1=l_0=l_1=r_0=r_1=0;sum=0;
    }
    void re1(){
        v=1;l0=0;l1=1;r0=0;r1=1;l_1=r_1=l_0=r_0=0;sum=1;
    }
    void re0(){
        v=1;l0=1;l1=0;r0=1;r1=0;l_1=r_1=l_0=r_0=0;sum=0;
    }
}imf[N],C;

node merge(node A,node B){
    C.clear();C.sum=A.sum+B.sum;C.v=A.v+B.v;
    C.sum+=(ll)((A.r0<A.v)?A.r_0+1:0)*(ll)B.l0+(ll)((B.l0<B.v)?B.l_0+1:0)*(ll)A.r0;
    C.sum+=calc1(A.r_1,A.r1+B.l1)-calc1(A.r_1,A.r1)+calc1(B.l_1,B.l1+A.r1)-calc1(B.l_1,B.l1);
    C.sum+=calc2(A.r1+B.l1)-calc2(A.r1)-calc2(B.l1);
    C.sum+=(A.r1<A.v?calc3(A.r1+B.l1)-calc3(A.r1):0)+(B.l1<B.v?calc3(A.r1+B.l1)-calc3(B.l1):0);
    if(A.v==A.l0)C.l0=A.l0+B.l0,C.l_0=B.l_0;else{
        C.l0=A.l0;
        if(A.l0+1+A.l_0==A.v)C.l_0=A.l_0+B.l0;else C.l_0=A.l_0;
    }
    if(A.v==A.l1)C.l1=A.l1+B.l1,C.l_1=B.l_1;else{
        C.l1=A.l1;
        if(A.l1+1+A.l_1==A.v)C.l_1=A.l_1+B.l1;else C.l_1=A.l_1;
    }
    if(B.v==B.r0)C.r0=A.r0+B.r0,C.r_0=A.r_0;else{
        C.r0=B.r0;
        if(B.r0+1+B.r_0==B.v)C.r_0=B.r_0+A.r0;else C.r_0=B.r_0;
    }
    if(B.v==B.r1)C.r1=A.r1+B.r1,C.r_1=A.r_1;else{
        C.r1=B.r1;
        if(B.r1+1+B.r_1==B.v)C.r_1=B.r_1+A.r1;else C.r_1=B.r_1;
    }
    return C;
}
int build(int h,int t){
    int tmp=++cnt,m=h+t>>1;imf[tmp].clear();
    if(h==t){
        if(a[h])imf[tmp].re1();else imf[tmp].re0();
        return tmp;
    }son[tmp][0]=build(h,m);son[tmp][1]=build(m+1,t);
    imf[tmp]=merge(imf[son[tmp][0]],imf[son[tmp][1]]);return tmp;
}
void change(int w,int h,int t){
    if(h==t){
        a[h]^=1;
        if(a[h])imf[w].re1();else imf[w].re0();
        return;
    }int m=h+t>>1;
    if(opl<=m)change(son[w][0],h,m);else change(son[w][1],m+1,t);
    imf[w]=merge(imf[son[w][0]],imf[son[w][1]]);
}
node query(int w,int h,int t){
    if(opl<=h && opr>=t)return imf[w];
    int m=h+t>>1;
    if(opl<=m && opr>m)return merge(query(son[w][0],h,m),query(son[w][1],m+1,t));else
    if(opl<=m)return query(son[w][0],h,m);else return query(son[w][1],m+1,t);
}

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    root=build(1,n);
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        int s,x,y;scanf("%d %d",&s,&x);opl=x;
        if(s==1)change(root,1,n);else{
            scanf("%d",&y);opl=x;opr=y;printf("%lld\n",(ll)(y-x+1)*(ll)(y-x+2)/2-query(root,1,n).sum);
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值