题意:
给定一个长度不超过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 为 true 时,Inc 指示是升序还是降序。
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;
}