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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章