机房测试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 */
第二种方法:
同样是线段树,但每个节点维护的是这段区间都是哪一种字符,如果这段区间有不同的字符,即为0。
修改时还是要区间求和,然后按升序(或降序)for每一种字符,如果这种字符有的话,就将对应的位置修改成它。修改时递归到整块都是同种的就暴力修改,然后打标记。pushdown时更简单。
相较于第一种,第二种方法更好掌握。
以上是关于机房测试1:string(线段树)的主要内容,如果未能解决你的问题,请参考以下文章