题解 [ABC341E] Alternating String
赛时用的线段树,赛后发现可以差分后用树状数组。
题意
给一个长度为 $N$ 由 $0/1$ 组成的数列,有 $Q$ 次操作。
- 将区间 $[L,R]$ 的所有数按位翻转。
- 查询 $[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;
}

浙公网安备 33010602011771号