• 机房测试1:string(线段树)


    题目:

     

     分析:

    暴力:每一次对区间暴力排序。

    优化:如果可以知道一个区间中有哪种字符,这些字符分别有多少个,就可以直接按字典序枚举,将它们快速地插入区间中了。

    题中有一个重要信息:只有小写字母,即只有26种字符。

    第一种方法:

    可以用一个线段树来维护,每个节点储存26个字符在这个区间中的对应情况。每次修改,就query统计出所求区间每一种字符的个数。

    然后再区间修改,具体见代码。

    #include<bits/stdc++.h>
    using namespace std;
    #define mid ((l+r)>>1)
    #define N 100005
    #define ri register int
    int num[26][N*4],now[N*4],a[N],fl[N*4];
    void update(int s) { for(ri i=0;i<26;++i) num[i][s]=num[i][s<<1]+num[i][s<<1|1]; }
    void build(int s,int l,int r)
    {
        fl[s]=-1;
        if(l==r){ num[a[l]][s]=1; return ; }//一维存颜色,一维存线段树节点 
        build(s<<1,l,mid); build(s<<1|1,mid+1,r);
        update(s);
    }
    void pushdown(int s,int l,int r)
    {
        if(fl[s]==-1) return ;
        int tmp=mid-l+1;
        if(fl[s]==1){//标记下传 就是将前半部分的字符按升序传给左区间 后半部分给右区间 
            int cnt=0,p=-1;
            while(cnt<tmp) p++,cnt+=num[p][s],num[p][s<<1]=num[p][s],num[p][s<<1|1]=0;
            cnt-=tmp;
            num[p][s<<1]-=cnt; num[p][s<<1|1]+=cnt;
            for(p++;p<26;p++) num[p][s<<1|1]=num[p][s],num[p][s<<1]=0;
        }
        else{
            int cnt=0,p=26;
            while(cnt<tmp) p--,cnt+=num[p][s],num[p][s<<1]=num[p][s],num[p][s<<1|1]=0;
            cnt-=tmp;
            num[p][s<<1]-=cnt; num[p][s<<1|1]+=cnt;
            for(p--;p>=0;p--) num[p][s<<1|1]=num[p][s],num[p][s<<1]=0;
        }
        fl[s<<1]=fl[s]; fl[s<<1|1]=fl[s]; fl[s]=-1;
    }
    void query(int s,int l,int r,int L,int R)
    {
        if(L<=l && r<=R){
            for(ri i=0;i<26;++i) now[i]+=num[i][s]; return ;
        }
        pushdown(s,l,r);
        if(L<=mid) query(s<<1,l,mid,L,R);
        if(R>mid)  query(s<<1|1,mid+1,r,L,R);
    }
    void modify(int s,int l,int r,int L,int R,int op)
    {
        if(l==r){
            int cnt=0,tmp=l-L+1,p;
            if(op){
                p=-1;
                while(cnt<tmp) p++,cnt+=now[p];
            }
            else{
                p=26;
                while(cnt<tmp) p--,cnt+=now[p];
            }
            for(ri i=0;i<26;++i) num[i][s]=0;
            num[p][s]=1;
            return ;
        }//我觉得这可能不需要 
        if(L<=l && r<=R){
            fl[s]=op;
            int cnt=0,tmp1=l-L+1,tmp2=r-L+1;//画线段理解 
            for(ri i=0;i<26;++i) num[i][s]=0;
            if(op){//升序 
                int p=-1;
                while(cnt<tmp1) p++,cnt+=now[p];//先从L跳到 l,用升序的字符跳位置 
                num[p][s]=cnt-tmp1+1;//走出去了一段 要减掉 
                while(cnt<tmp2) p++,num[p][s]=now[p],cnt+=now[p];//从l到 r,依次按升序修改此区间的字符种类 
                num[p][s]-=cnt-tmp2;//同上,减去跳出去的一段 
            }
            else{
                int p=26;
                while(cnt<tmp1) p--,cnt+=now[p];
                num[p][s]=cnt-tmp1+1;
                while(cnt<tmp2) p--,num[p][s]=now[p],cnt+=now[p];
                num[p][s]-=cnt-tmp2;
            }
            return ;
        }
        pushdown(s,l,r);
        if(L<=mid) modify(s<<1,l,mid,L,R,op);
        if(R>mid)  modify(s<<1|1,mid+1,r,L,R,op);
        update(s);
    }
    void print(int s,int l,int r)
    {
        if(l==r){//递归到叶子节点输出 
            for(ri i=0;i<26;++i) if(num[i][s]) { printf("%c",i+'a'); break; }
            return ;
        }
        pushdown(s,l,r);
        print(s<<1,l,mid); print(s<<1|1,mid+1,r);
    }
    char s[N];
    int main()
    {
        freopen("string.in","r",stdin);
        freopen("string.out","w",stdout);
        int n,m,l,r,op;
        scanf("%d%d",&n,&m);
        scanf("%s",s);
        for(ri i=1;i<=n;++i) a[i]=s[i-1]-'a';
        build(1,1,n);
        while(m--){
            scanf("%d%d%d",&l,&r,&op);
            for(ri i=0;i<26;++i) now[i]=0;
            query(1,1,n,l,r);//统计区间中每种字符有多少个 
            modify(1,1,n,l,r,op);
        }
        print(1,1,n);
    }
    /*
    5 2
    cabcd
    1 3 1
    3 5 0
    
    5
    2
    trlyo
    1 4 1
    1 1 0
    */
    1

    第二种方法:

    同样是线段树,但每个节点维护的是这段区间都是哪一种字符,如果这段区间有不同的字符,即为0。

    修改时还是要区间求和,然后按升序(或降序)for每一种字符,如果这种字符有的话,就将对应的位置修改成它。修改时递归到整块都是同种的就暴力修改,然后打标记。pushdown时更简单。

    相较于第一种,第二种方法更好掌握。

    2

  • 相关阅读:
    this和e.target的异同
    onmouseover和onmouseenter区别
    jquery带参插件函数的编写
    让bind函数支持IE8浏览器的方法
    LeetCode #9 Palindrome Number
    LeetCode #7 Reverse Integer
    LeetCode #1 Two Sum
    iOS 真机测试错误“The application bundle does not contain a valid identifier”
    Swift、Objective-C 单例模式 (Singleton)
    iOS 相册和网络图片的存取
  • 原文地址:https://www.cnblogs.com/mowanying/p/11604603.html
Copyright © 2020-2023  润新知