题目:
分析:
暴力:每一次对区间暴力排序。
优化:如果可以知道一个区间中有哪种字符,这些字符分别有多少个,就可以直接按字典序枚举,将它们快速地插入区间中了。
题中有一个重要信息:只有小写字母,即只有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时更简单。
相较于第一种,第二种方法更好掌握。
原文地址:https://www.cnblogs.com/mowanying/p/11604603.html
时间: 2024-10-10 14:36:21