题意:
给定一个长度不超过10^5的字符串(小写英文字母),和不超过5000个操作。
每个操作 L R K 表示给区间[L,R]的字符串排序,K=1为升序,K=0为降序。
最后输出最终的字符串。
首先,发现每个元素的值只有26种,很自然的想到了计数排序。
由于线段树可以将任意区间分成不超过2*log2(n)个子区间。
所以,首先用线段树统计出所求区间的各个字符的数量。
然后按照排序的结果,依次将这些数存入分成的子区间,向上更新结果就好了。
复杂度O(26*q*log2(n)) (q为询问数量,n为字符串长度)
线段树节点定义如下:
<span style="font-size:14px;">struct Node{ int d[26];//计数排序 int D;//总数 bool sorted;//是否排好序 bool Inc;//是否升序 }; </span>
sorted 标记是覆盖式标记,也就是说,一旦某节点sorted 为 true 那么这个节点的所有子节点的值都无效。
最后操作完,下推所有标记,然后输出字符串即可。
代码:
#include <iostream> #include <cstdio> #include <cmath> #include <cstring> #define out(i) <<#i<<"="<<(i)<<" " #define OUT1(a1) cout out(a1) <<endl #define OUT2(a1,a2) cout out(a1) out(a2) <<endl #define OUT3(a1,a2,a3) cout out(a1) out(a2) out(a3)<<endl #define maxn 100007 #define ls l,m,rt<<1 #define rs m+1,r,rt<<1|1 using namespace std; //std int n,q; char str[maxn]; //segment tree struct Node{ int d[26];//计数排序 int D;//总数 bool sorted;//是否排好序 bool Inc;//是否升序 void Insert(int v){ memset(d,0,sizeof(d)); D=d[v]=1; sorted=false; } void Take(bool Left,int N){//取本区间的N个数,Left指示从左开始还是从右开始 D=N; if(Left){ for(int i=0;i<26;++i){ if(N>=d[i]) N-=d[i]; else{d[i]=N;N=0;} } } else{ for(int i=25;i>=0;--i){ if(N>=d[i]) N-=d[i]; else{d[i]=N;N=0;} } } } void Drop(bool Left,int N){//去掉本区间的N个数,Left指示从左开始还是从右开始 D=D-N; if(Left){ for(int i=0;i<26;++i){ if(N>=d[i]) N-=d[i],d[i]=0; else{d[i]-=N;N=0;} } } else{ for(int i=25;i>=0;--i){ if(N>=d[i]) N-=d[i],d[i]=0; else{d[i]-=N;N=0;} } } } Node operator +(const Node &B)const{//重载加法,更新节点 Node C; C.sorted=false; for(int i=0;i<26;++i) C.d[i]=d[i]+B.d[i]; C.D=D+B.D; return C; } void show(){ printf("D=%d\n",D); for(int i=0;i<26;++i) { if(d[i]) printf("d[%d]=%d\n",i,d[i]); } } }ST[maxn<<2]; void PushDown(int rt){//下推标记,直接覆盖子节点 Node &L=ST[rt<<1],&R=ST[rt<<1|1]; if(ST[rt].sorted){ int N=L.D; L=ST[rt]; L.Take(ST[rt].Inc,N); N=R.D; R=ST[rt]; R.Take(!ST[rt].Inc,N); ST[rt].sorted=false; } } void Build(int l,int r,int rt){//建树 if(l==r){ST[rt].Insert(str[l]-'a');return;} int m=(l+r)>>1; Build(ls); Build(rs); ST[rt]=ST[rt<<1]+ST[rt<<1|1]; } Node Query(int L,int R,int l,int r,int rt){//查询区间 if(L <= l && r <= R){ return ST[rt]; } PushDown(rt); int m=(l+r)>>1; Node LANS,RANS; int X=0; if(L <= m) LANS=Query(L,R,ls),X+=1; if(R > m) RANS=Query(L,R,rs),X+=2; if(X==1) return LANS; if(X==2) return RANS; return LANS+RANS; } Node Sum; void Update(int L,int R,int l,int r,int rt){ if(L <= l && r <= R){ int N=ST[rt].D; ST[rt]=Sum; ST[rt].Take(Sum.Inc,N); Sum.Drop(Sum.Inc,N); return; } int m=(l+r)>>1; if(L <= m) Update(L,R,ls); if(R > m) Update(L,R,rs); ST[rt]=ST[rt<<1]+ST[rt<<1|1]; } void Sort(int L,int R,int Inc){ Sum=Query(L,R,1,n,1);//求出区间总和 Sum.sorted=true;Sum.Inc=Inc;//设置标记 Update(L,R,1,n,1);//分配总和给各个子区间,并且向上更新 } void Down(int l,int r,int rt){//下推所有标记 if(l==r){ for(int i=0;i<26;++i){ if(ST[rt].d[i]){ str[l]=i+'a'; break; } } return; } PushDown(rt); int m=(l+r)>>1; Down(ls); Down(rs); } int main(void) { while(~scanf("%d%d",&n,&q)){ scanf("%s",str+1); Build(1,n,1);//建树 for(int i=0;i<q;++i){ int x,y,k; scanf("%d%d%d",&x,&y,&k); Sort(x,y,k);//操作 } Down(1,n,1);//下推所有标记 printf("%s\n",str+1); } return 0; }