题解 [ABC341E] Alternating String

赛时用的线段树,赛后发现可以差分后用树状数组。

题意

给一个长度为 $N$ 由 $0/1$ 组成的数列,有 $Q$ 次操作。

  1. 将区间 $[L,R]$ 的所有数按位翻转。
  2. 查询 $[L,R]$ 中是否相邻两个数都不同。

分析

线段树做法

看到区间修改区间查询,很容易想到线段树。

用线段树维护区间信息:

  • $L$ 表示该节点最左端的数值。
  • $R$ 表示该节点最右端的数值。
  • $ans$ 表示该节点是否两两不同。
  • $lazy$ 表示该节点的懒标记(是否被翻转)。

区间的左端点和右端点不需要特别的存储,在递归的时候可以直接带上端点。

一个区间的按位翻转不会影响到这个区间是否相邻两个数两两不同,所以下放懒标记时不需要特别处理 $ans$,只需要处理 $L,R$。

上传的时候该区间是否两两不同需要判断左儿子、右儿子是否两两不同,且左儿子的最右端不等于右儿子的最左端。

特别的,只有一个节点也算作两两不同,建树时需要注意。

差分 + 树状数组做法

前后两个数不同则差分为 $1$,否则为 $0$。

修改一个区间 $[L,R]$ 只需要在差分数组 $L$ 和 $R+1$ 上取反。

如果一个区间 $[L,R]$ 两两不同,则差分数组 $[L+1,R]$ 都为 $1$,即差分数组上这一段的和为 $R-L$。

于是这个问题变成了单点修改区间查询,可以用树状数组解决。

两种解法的时间复杂度均为 $O(Q \log N)$,但是后者常数更小所以运行效率较高。

代码

线段树做法

//the code is from chenjh
#include<cstdio>
#define MAXN 500005
#define lson (rt<<1)
#define rson (rt<<1|1)
using namespace std;
using ci=const int;
int n,q;
bool a[MAXN];
struct Node{
    bool L,R,ans,lazy;
    Node(bool _L=0,bool _R=0,bool _ans=0,bool _lazy=0):L(_L),R(_R),ans(_ans),lazy(_lazy){} 
}t[MAXN<<2];
void build(ci rt,ci l,ci r){//建树。
    if(l==r){
        t[rt].L=t[rt].R=a[l];
        t[rt].ans=1;//只有一个点也算作两两不同。
        return;
    }
    int mid=(l+r)>>1;
    build(lson,l,mid),build(rson,mid+1,r);
    t[rt].L=t[lson].L,t[rt].R=t[rson].R;
    t[rt].ans=t[lson].ans&&t[rson].ans&&t[lson].R!=t[rson].L;//上传答案。
}
void pd(ci rt,ci l,ci r){
    if(t[rt].lazy){//下放懒标记。
        t[lson].lazy^=1,t[lson].L^=1,t[lson].R^=1;
        t[rson].lazy^=1,t[rson].L^=1,t[rson].R^=1;
        t[rt].lazy=0;
    }
}
void update(ci rt,ci l,ci r,ci L,ci R){
    if(L<=l && r<=R){
        t[rt].lazy^=1,t[rt].L^=1,t[rt].R^=1;
        return;
    }
    pd(rt,l,r);
    int mid=(l+r)>>1;
    if(L<=mid) update(lson,l,mid,L,R);
    if(mid<R) update(rson,mid+1,r,L,R);
    t[rt].L=t[lson].L,t[rt].R=t[rson].R;
    t[rt].ans=t[lson].ans&&t[rson].ans&&t[lson].R!=t[rson].L;
}
Node query(ci rt,ci l,ci r,ci L,ci R){
    if(L<=l && r<=R) return t[rt];
    pd(rt,l,r);
    int mid=(l+r)>>1;
    Node ret;
    if(L<=mid && mid<R){//左右儿子区间都要查询。
        Node rl=query(lson,l,mid,L,R),rr=query(rson,mid+1,r,L,R);
        ret=Node(rl.L,rr.R,rl.ans&&rr.ans&&rl.R!=rr.L,0);//合并两个区间。
    }
    else if(L<=mid)//只有左儿子需要查询。
        ret=query(lson,l,mid,L,R);
    else if(mid<R)//只有右儿子需要查询。
        ret=query(rson,mid+1,r,L,R);
    return ret;
}
int main(){
    scanf("%d %d ",&n,&q);
    for(int i=1;i<=n;i++) a[i]=getchar()&1;
    build(1,1,n);
    for(int op,l,r;q--;){
        scanf("%d%d%d",&op,&l,&r);
        if(op==1) update(1,1,n,l,r);
        else if(op==2) puts(query(1,1,n,l,r).ans?"Yes":"No");
    }
    return 0;
}

差分 + 树状数组做法

//the code is from chenjh
#include<cstdio>
#include<cstring>
#define MAXN 500005
using namespace std;
template<typename T>
struct fenwick_tree{
    public:
        fenwick_tree(int _SIZE=0):SIZE(_SIZE){dt=new T[SIZE+1]();memset(dt,0,sizeof(T)*(SIZE+1));}
        fenwick_tree(const fenwick_tree& y):SIZE(y.size()),dt(new T[y.size()+1]){memcpy(dt,y.get_dt(),sizeof(T)*(SIZE+1));}
        ~fenwick_tree(){delete[] dt;}
        const T&operator [] (const int&x)const{return dt[x];}
        fenwick_tree&operator = (const fenwick_tree&y){if(this!=&y){SIZE=y.size();T*new_dt=new T[SIZE+1]();memcpy(new_dt,y.get_dt(),sizeof(T)*(SIZE+1));delete[] dt;dt=new_dt;}return *this;}
        void resize(int _SIZE){T*new_dt =new T[_SIZE+1]();memcpy(new_dt,dt,sizeof(T)*((SIZE<_SIZE?SIZE:_SIZE)+1));delete[] dt;dt=new_dt,SIZE=_SIZE; }
        void clear(){SIZE=0;delete[] dt;dt=new T[SIZE+1]();memset(dt,0,sizeof(T)*(SIZE+1));}
        int size()const{return SIZE;}
        T* get_dt()const{return dt;}
        void add(int x,const T&v){for(;x<=SIZE;x+=x&-x)dt[x]+=v;}
        T sum(const int&l,const int&r)const{return sum(r)-sum(l-1);}
    private:
        T*dt=nullptr;
        int SIZE;
        T sum(int x)const{T ret(0);for(;x;x^=x&-x)ret+=dt[x];return ret;}
};
int n,q;
bool a[MAXN];
int main(){
    scanf("%d %d ",&n,&q);
    for(int i=1;i<=n;i++) a[i]=getchar()^'0';
    fenwick_tree<int> T(n);//树状数组。
    for(int i=n;i>0;--i)if(a[i]^=a[i-1])T.add(i,1);//求出差分数组。
    for(int p,l,r;q--;){
        scanf("%d%d%d",&p,&l,&r);
        if(p==1){
            T.add(l,T.sum(l,l)?-1:1);//如果为 1 就修改为 0,否则修改为 1.
            if(r<n) T.add(r+1,T.sum(r+1,r+1)?-1:1);
        }
        else if(p==2) puts(l==r||T.sum(l+1,r)==r-l?"Yes":"No");//特判只有一个点的情况。
    }
    return 0;
}
posted @ 2024-02-18 10:49  Chen_Jinhui  阅读(39)  评论(0)    收藏  举报  来源