G. Anya and the Mysterious String(附线段树基础解惑)

Problem - 1881G - Codeforces

线段树赋值情况一定要落到每个子节点独自的变量上,赋值于原数组无意义;

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;
}

  • 16
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值