线段树赋值情况一定要落到每个子节点独自的变量上,赋值于原数组无意义;
push_down 仅下放已有的 tag 值,对于修改过程中的 k 则通过 merge 实现向上回溯;
线段树用于处理满足结合律的问题,故 merge 操作是核心;
update 和 query 过程虽相似,但二者有本质区别;
update 侧重于拆分,其通过尽可能的拆分来找到所有合法节点,并对其修改。而他的合并是面向子节点的所有父节点,故 merge 较为简单,仅需与递归同步合并即可;
query 侧重于合并,由于查询区间不能合并含非目标区间点的值,为保证其严谨性,则其递归法则与 update 是相反的。特别的,在无法仅使用一个子节点完成递归时,query 就需要合并当前父节点下两个子节点当中的合法区间,此时则需同时递归两个子节点继续查询其中的合法项;
(洛谷的线段树模板部分题解具有一定误导性,特别是其 update 和 query 的高度相似使得初学者不易于深入理解其操作本质,而停滞于背模板,没错就是我)
由于线段树操作的基本单位是区间,故使用线段树时,大多情况下以子节点作为返回类型, 而不是单个值;这样方便合并操作以及多个状态量的访问和记录;
#include <bits/stdc++.h>
using namespace std;
const int max_n=2e5+5;
int n,q;
string s;
struct node{int l,r,l2,r2,v;}tr[max_n<<2];
int tag[max_n<<2],a[max_n];
inline int ls(int p){return p<<1;}
inline int rs(int p){return p<<1|1;}
node merge_q(int ql,int qr,int l,int r,int p);
node merge(int p)
{
node ret,trl=tr[ls(p)],trr=tr[rs(p)];
ret.v=trl.v+trr.v;
if(trl.r==trr.l)ret.v++;
ret.l=trl.l;
ret.r=trr.r;
if(trl.r2!=-1&&trl.r2==trr.l)ret.v++;
if(trr.l2!=-1&&trr.l2==trl.r)ret.v++;
if(trl.l2==-1)ret.l2=trr.l;
else ret.l2=trl.l2;
if(trr.r2==-1)ret.r2=trl.r;
else ret.r2=trr.r2;
return ret;
}
void push_down(int p)
{
if(tag[p]){
tag[ls(p)]=(tag[ls(p)]+tag[p])%26;
tag[rs(p)]=(tag[rs(p)]+tag[p])%26;
node trl=tr[ls(p)],trr=tr[rs(p)];
trl.l=(trl.l+tag[p])%26;
trl.r=(trl.r+tag[p])%26;
if(trl.l2!=-1){
trl.l2=(trl.l2+tag[p])%26;
trl.r2=(trl.r2+tag[p])%26;
}
trr.l=(trr.l+tag[p])%26;
trr.r=(trr.r+tag[p])%26;
if(trr.l2!=-1){
trr.l2=(trr.l2+tag[p])%26;
trr.r2=(trr.r2+tag[p])%26;
}
tr[ls(p)]=trl;
tr[rs(p)]=trr;
tag[p]=0;
}
}
void update(int ql,int qr,int l,int r,int p,int k)
{
if(ql<=l&&r<=qr){
tag[p]=(tag[p]+k)%26;
tr[p].l=(tr[p].l+k)%26;
tr[p].r=(tr[p].r+k)%26;
if(tr[p].l2!=-1){
tr[p].l2=(tr[p].l2+k)%26;
tr[p].r2=(tr[p].r2+k)%26;
}
return;
}
int mid=(l+r)>>1;
push_down(p);
if(ql<=mid)update(ql,qr,l,mid,ls(p),k);
if(qr>mid)update(ql,qr,mid+1,r,rs(p),k);
tr[p]=merge(p);
}
node query(int ql,int qr,int l,int r,int p)
{
if(ql<=l&&r<=qr){
return tr[p];
}
int mid=(l+r)>>1;
push_down(p);
if(qr<=mid)return query(ql,qr,l,mid,ls(p));
if(ql>mid)return query(ql,qr,mid+1,r,rs(p));
return merge_q(ql,qr,l,r,p);
}
node merge_q(int ql,int qr,int l,int r,int p)
{
int mid=(l+r)>>1;
node ret,trl=query(ql,qr,l,mid,ls(p)),trr=query(ql,qr,mid+1,r,rs(p));
ret.v=trl.v+trr.v;
if(trl.r==trr.l)ret.v++;
ret.l=trl.l;
ret.r=trr.r;
if(trl.r2!=-1&&trl.r2==trr.l)ret.v++;
if(trr.l2!=-1&&trr.l2==trl.r)ret.v++;
if(trl.l2==-1)ret.l2=trr.l;
else ret.l2=trl.l2;
if(trr.r2==-1)ret.r2=trl.r;
else ret.r2=trr.r2;
return ret;
}
void build(int p,int l,int r)
{
tag[p]=0;
if(l==r){
tr[p]={(int)s[l]-'a',(int)s[l]-'a',-1,-1,0};
return;
}
int mid=(l+r)>>1;
build(ls(p),l,mid);
build(rs(p),mid+1,r);
tr[p]=merge(p);
}
void solve()
{
cin>>n>>q>>s;
s="#"+s;
build(1,1,n);
while(q--){
int opt;cin>>opt;
if(opt==1){
int l,r,k;cin>>l>>r>>k;
update(l,r,1,n,1,k);
}else{
int l,r;cin>>l>>r;
node ans=query(l,r,1,n,1);
cout<<(ans.v==0?"YES\n":"NO\n");
}
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int _t;cin>>_t;
while(_t--){
solve();
}
return 0;
}