(线段树,01序列)[ABC341E] Alternating String题解
题意
给定一个 字符串 ,定义相邻字符均不同的字符串为好串,要求支持两种操作。
-
1 L R
,将从 到 的区间取反。 -
2 L R
,查询 从 到 的子串是否为好串。
分析
没有接触过这种题的建议去看一眼 P6492。
比较板子,对于这种题,一般都是去维护区间的两个端点。
一个区间左儿子的左端点便是这个区间的左端点,一个区间右儿子的右端点便是这个区间的右端点。
合并的话,首先要考虑拼接处的左右儿子是否是好串,其次要考虑拼接处两个端点的字符是否不同,最后 pushdown
的时候记得取反就可以了。
线段树相比其他做法代码冗长,常数也大,但是逻辑相对简单,好想好写。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+5;
int n,q,a[maxn];
struct node {
int l,r;
bool ans;
int pre,suf;
int tag;
} t[maxn*4];
void pushup(int p) {
t[p].ans=(t[p*2].ans && t[p*2+1].ans && (t[p*2].suf!=t[p*2+1].pre));
t[p].pre=t[p*2].pre;
t[p].suf=t[p*2+1].suf;
}
void pushdown(int p) {
if(t[p].tag) {
t[p*2].tag^=1;
t[p*2+1].tag^=1;
t[p*2].pre= !t[p*2].pre;
t[p*2].suf= !t[p*2].suf;
t[p*2+1].pre= !t[p*2+1].pre;
t[p*2+1].suf= !t[p*2+1].suf;
t[p].tag=0;
}
}
void build(int p,int l,int r) {
t[p].l=l,t[p].r=r;
t[p].tag=0;
if(l==r) {
t[p].ans=1;
t[p].pre=t[p].suf=a[l];
return;
}
int mid=(l+r)/2;
build(p*2,l,mid);
build(p*2+1,mid+1,r);
pushup(p);
}
void change(int p,int l,int r) {
if(r<t[p].l or t[p].r<l)
return;
if(l<=t[p].l && t[p].r<=r) {
t[p].tag^=1;
t[p].pre= !t[p].pre;
t[p].suf= !t[p].suf;
return;
}
pushdown(p);
change(p*2,l,r);
change(p*2+1,l,r);
pushup(p);
}
bool cmp(node a,node b) {
return (a.ans && b.ans && a.suf!=b.pre);
}
int check1(node a,node b) {
if(a.pre==-1)
return b.pre;
return a.pre;
}
int check2(node a,node b) {
if(b.suf==-1)
return a.suf;
return b.suf;
}
node ask(int p,int l,int r) {
if(r<t[p].l or t[p].r<l)
return {-1,-1,1,-1,-1,-1};
if(l<=t[p].l && t[p].r<=r)
return t[p];
pushdown(p);
node a=ask(p*2,l,r),b=ask(p*2+1,l,r);
return {-1,-1,cmp(a,b),check1(a,b),check2(a,b),-1};
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>q;
for(int i=1; i<=n; i++) {
char ch;
cin>>ch;
a[i]=ch-'0';
}
build(1,1,n);
while(q--) {
int op,l,r;
cin>>op>>l>>r;
if(op==1)
change(1,l,r);
if(op==2) {
if(ask(1,l,r).ans)
cout<<"Yes\n";
else
cout<<"No\n";
}
}
return 0;
}