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

Posted mowanying

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了机房测试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

以上是关于机房测试1:string(线段树)的主要内容,如果未能解决你的问题,请参考以下文章

[线段树]JZOJ 5812

hdu 1166敌兵布阵(线段树入门题)

数据结构--线段树

[题解]线段树专题测试2017.1.21

string [线段树优化桶排]

高级数据结构线段树