AtCoder Beginner Contest 157 E - Simple String Queries (线段树+二进制 or 二维树状数组 or set+二分)

题目链接

题目大意

题意:有一个长度为N的字符串(只包含小写字母)。

现有Q个操作,操作1是把第x位的字符改成y,操作2是查询[l,r]内去重后有多少个字符。

前记

这个题目个人认为很有研究的必要。我将用三个方法来写这题

线段树+二进制

首先看到单点操作和区间查询,很容易想到线段树操作。但是一直没想出,后面看别人代码明白可以用二进制操作。

把a−z用0−25表示,再把他们用二进制位表示,就可以用或运算和线段树单点修改区间查询解决这道问题,时间复杂度O(nlogn)

知识

__builtin_popcount()可以计算int类型的数据二进制有多少个1

代码

#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=5e5+5;
int n,t,opt,tree[maxn<<2],pos,x,y;
char s[maxn],ch;
void build(int node,int l,int r){
    if(l==r){
        tree[node]=1<<(s[l]-'a');
        return ;
    }
    int mid=(l+r)>>1;
    build(node<<1,l,mid);
    build(node<<1|1,mid+1,r);
    tree[node]=tree[node<<1]|tree[node<<1|1];
}
void change(int node,int l,int r,int pos){
    if(l==r&&l==pos){
        tree[node]=1<<(ch-'a');
        return ;
    }
    int mid=(l+r)>>1;
    if(mid>=pos)    change(node<<1,l,mid,pos);
    else            change(node<<1|1,mid+1,r,pos);
    tree[node]=tree[node<<1]|tree[node<<1|1];
}
int query(int node,int L,int R,int l,int r){
    if(L<=l&&R>=r){
        return tree[node];
    }
    int mid=(l+r)/2,ans=0;
    if(mid>=L) ans=ans|query(node<<1,L,R,l,mid);
    if(mid<R)  ans=ans|query(node<<1|1,L,R,mid+1,r);
    return ans;
}
int main(){
    scanf("%d %s %d",&n,s+1,&t);
    build(1,1,n);
    while(t--){
        scanf("%d",&opt);
        if(opt==1){
            scanf("%d %c",&pos,&ch);
            if(s[pos]!=ch){
                change(1,1,n,pos);
                s[pos]=ch;//改变
            }
        }else{
            scanf("%d %d",&x,&y);
            printf("%d\n",__builtin_popcount(query(1,x,y,1,n)));
        }
    }
    return 0;
}


set+二分

修改s[i]显然比较简单,问题是解决l到r之间有多少种字母,这里显然要用二分查找降低复杂度,二分查找又需要有序的数据,所以需要用到

set。以前觉得set没什么用,现在感觉set比优先队列好很多。可以插入删除随便哪一点的元素。

知识

lower_bound(key_value) ,返回第一个大于等于key_value的定位器

upper_bound(key_value),返回最后一个大于key_value的定位器(也就是指针

二分使用方法和普通的数组不同

set.lower_bound(x) 返回大于等于x的第一个迭代器(指针)

还有这种迭代器不能加减,没有定义运算符

也就是如果在查找[x,y]有多少个元素不能set.upper_bound(y)-set.lower_bound(x)

因为没有定义这个运算符

只能找大于等于他的最小元素

新学的tip:

set中先加入较大的数防止越界(重要

代码

#include<cstdio>
#include<set>
#include<algorithm>
using namespace std;
const int maxn=5e5+5;
char s[maxn],ch;
set<int> pos[26];
int n,t,opt,x,y;
int main(){
    scanf("%d %s %d",&n,s+1,&t);
    for(int i=0;i<=25;i++){
        pos[i].insert(n+1);//防止二分为空
    }
    for(int i=1;i<=n;i++){//放入相应的set
        pos[s[i]-'a'].insert(i);
    }
    while(t--){
        scanf("%d",&opt);
        if(opt==1){
            scanf("%d %c",&x,&ch);
            if(s[x]!=ch){
                pos[s[x]-'a'].erase(x);//删除
                pos[ch-'a'].insert(x);//加入
                s[x]=ch;
            }
        }else{
            scanf("%d %d",&x,&y);
            int ans=0;
            for(int i=0;i<=25;i++){
                if(*pos[i].lower_bound(x)<=y){//如果大于等于x的最小值小于等于y则有这个字符
                    ans++;
                }
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}

二维树状数组

其实思考思考会发现造26个树状数组就直接ok了。显然是最容易且最好理解的

代码

#include<cstdio>
using namespace std;
const int maxn=5e5+5;
char ch,s[maxn];
int n,bit[maxn][30],opt,l,r,t;
int lowbit(int x){
    return x&(-x);
}
void update(int pos,int kind,int cnt){//位置,种类,增减
    while(pos<=n){
        bit[pos][kind]+=cnt;
        pos=pos+lowbit(pos);
    }
}
int query(int x,int i){
    int ans=0;
    while(x>0){
        ans+=bit[x][i];
        x=x-lowbit(x);
    }
    return ans;
}
int main(){
    scanf("%d %s %d",&n,s+1,&t);
    for(int i=1;i<=n;i++){
        update(i,s[i]-'a',1);//加1
    }
    while(t--){
        scanf("%d",&opt);
        if(opt==1){
            scanf("%d %c",&l,&ch);
            if(s[l]!=ch){
                update(l,s[l]-'a',-1);//减1
                update(l,ch-'a',1);//加1
                s[l]=ch;//修改不要忘了
            }
        }else{
            scanf("%d %d",&l,&r);
            int ans=0;
            for(int i=0;i<=25;i++){//查询26个树状数组
                if(query(r,i)-query(l-1,i)){
                    ans++;
                }
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}

发布了68 篇原创文章 · 获赞 2 · 访问量 2255

猜你喜欢

转载自blog.csdn.net/m0_46209312/article/details/105118253