Substring of Sorted String 题解

发布时间 2023-06-05 17:02:13作者: TKXZ133

Substring of Sorted String

写篇题解纪念一下蒟蒻第一次赛时切出的 F 题。

题目简述

对一个字符串进行单点修改,区间判断操作。

修改操作为将一个字符修改为另一个,判断操作为判断区间是否是整个字符串升序排序后的字串。

思路分析

蒟蒻第一眼线段树,但刚开始没仔细看题,以为是判断区间是否升序排序,所以很快得到做法:

对于每个线段树上的节点,保存三个信息:区间最左端的字符 \(\text{lz}\),区间最右端的字符 \(\text{rz}\),区间是否升序排序 \(\text{is}\)

合并时,我们按照如下方法合并:(记整个区间的编号为 \(\text{p}\),左区间的编号为 \(\text{lson}\),右区间的编号为 \(\text{rson}\)。)

lz[p]=lz[lson]
rz[p]=rz[rson]
is[p]=is[lson]&&is[rson]&&(rz[lson]<=ls[rson])

至于为什么这样合并是显然的。

但在蒟蒻写完交了一发后,发现 WA 了,仔细看题后发现是判断区间是否是整个字符串是否是整个字符串升序排序后的字串。

但在思考后,蒟蒻得到了一个做法:

在每个节点上额外维护一个数组 \(\text{cnt}\)\(\text{cnt}[i]\) 表示在这个整个区间内第 \(i\) 个小写字母出现的个数,合并时只需要对于枚举每一个字符相加就行。

在判断时,我们在升序排序的基础上,判断这个区间内除了最左端字符和最右端字符之外的字符出现次数是否等于整个字符串的该字符出现次数就可以了,这样是可以保证没有问题的。

时间复杂度为 \(O(26q\log n)\),虽然常数比 \(\log\) 还大,但不影响正确性。

注意事项

单点修改后记得将之前的位置清零。

注意下标的同步。

代码

放上丑陋的赛时代码:

#include <bits/stdc++.h>
using namespace std;
const int N=100100;

int n,q,op,in1,in3;
char inp[N];//整个字符串
char in2[2];//输入的操作类型

struct STn{int l,r,is;char lz,rz;int cnt[30];};//线段树的节点
void merge(STn &res,STn a,STn b){//合并区间
    res.lz=a.lz;
    res.rz=b.rz;
    res.is=(a.is&&b.is)&&(a.rz<=b.lz);//更新
    for(int i=1;i<=26;i++)
        res.cnt[i]=a.cnt[i]+b.cnt[i];//相加
}
struct ST{
    STn a[N<<2];
    void build(int p,int l,int r){
        a[p].l=l;a[p].r=r;
        if(a[p].l==a[p].r){a[p].lz=a[p].rz=inp[a[p].l];a[p].is=1;a[p].cnt[inp[a[p].l]-'a'+1]=1;return ;}//初始化
        int mid=(a[p].l+a[p].r)>>1;
        build(p<<1,l,mid);build(p<<1|1,mid+1,r);
        merge(a[p],a[p<<1],a[p<<1|1]);return ;
    }
    void change(int p,int x,char k){
        if(a[p].l==a[p].r){a[p].cnt[a[p].lz-'a'+1]=0;a[p].lz=a[p].rz=k;a[p].cnt[k-'a'+1]=1;return ;}//单点修改后重置
        int mid=(a[p].l+a[p].r)>>1;
        if(x<=mid) change(p<<1,x,k);else change(p<<1|1,x,k);
        merge(a[p],a[p<<1],a[p<<1|1]);return ;
    }
    STn ask(int p,int l,int r){
        if(l<=a[p].l&&a[p].r<=r) return a[p];
        int mid=(a[p].l+a[p].r)>>1;
        if(r<=mid) return ask(p<<1,l,r);
        if(l>mid) return ask(p<<1|1,l,r);
        STn res;merge(res,ask(p<<1,l,r),ask(p<<1|1,l,r));//询问合并
        return res;
    }
}tree;

int main(){
    scanf("%d",&n);
    scanf("%s",inp+1);
    scanf("%d",&q);
    tree.build(1,1,n);
    while(q--){
        scanf("%d",&op);
        if(op==1){scanf("%d%s",&in1,in2+1);tree.change(1,in1,in2[1]);}
        if(op==2){
            scanf("%d%d",&in1,&in3);
            STn res=tree.ask(1,in1,in3);
            if(res.is){//前提是区间升序排序
                int f=1;
                for(int i=res.lz+1;i<=res.rz-1;i++)
                    if(res.cnt[i-'a'+1]!=tree.a[1].cnt[i-'a'+1]){//tree.a[1]就是整个字符串
                        f=0;break;
                    } //判断字符出现次数
                puts(f?"Yes":"No");
            }
            else puts("No");
        }
    }
    return 0;
}