码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2019-10-01 16:33:57      阅读:101      评论:0      收藏:0      [点我收藏+]

标签:字典序   方法   字母   show   插入   优化   输出   线段树   颜色   

题目:

技术图片

 

 技术图片

 

 分析:

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

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

题中有一个重要信息:只有小写字母,即只有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

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

标签:字典序   方法   字母   show   插入   优化   输出   线段树   颜色   

原文地址:https://www.cnblogs.com/mowanying/p/11604603.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!