题目描述:
给定一个由小写字母组成的字符串 s。有 m 次操作,每次操作给 定 3 个参数 l,r,x。如果 x=1,将 s[l]~s[r]升序排序;如果 x=0,将 s[l]~s[r] 降序排序。你需要求出最终序列。
n<=100000
题目分析:
小写字母只有26种。
排序相当于把区间内的所有字母拿出来,然后a,b,c,d…依次放。
容易想到26棵动态开点线段树或者线段树上维护26种字母的个数。
我写的是枚举每种字母,清空线段树,把比它小的设为0,比它大的设为2,排序时统计个数就知道它应该放的位置然后区间赋值为1,继续下一次排序。常数比较大,要跑1s多一点,然而自己造随机数据0.2s,还以为可以过,结果这个算法在随机数据下(每次排序的区间范围比较大)可以避免一些重复赋值,要是区间小一点就会飞。。。
懒得再打线段树就写了一发无旋Treap,思想基本上和动态开点线段树一样,只是重新赋值的时候可以合并成一个点,所以总点数是n*26的,写个空间回收空间就是O(n)的,每次删除一段区间统计个数就不需要维护子树信息了,直接暴力遍历删除,方便快捷。
Code:
#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
int n,m,lc[maxn],rc[maxn],L[maxn],R[maxn],v[maxn],rnd[maxn],bin[maxn],top,tot,rt;
char s[maxn]; int cnt[26];
inline int Newnode(int l,int r,int x){
int cur=top?bin[top--]:++tot;
L[cur]=l,R[cur]=r,v[cur]=x,rnd[cur]=rand()<<15|rand();
return cur;
}
void merge(int &t,int a,int b){
if(!a||!b) {t=a+b;return;}
if(rnd[a]<rnd[b]) t=a,merge(rc[t],rc[a],b);
else t=b,merge(lc[t],a,lc[b]);
}
void split(int t,int &a,int &b,int k){
if(!t) {a=b=0;return;}
if(k<L[t]) b=t,split(lc[t],a,lc[b],k);
else if(k>=R[t]) a=t,split(rc[t],rc[a],b,k);
else rc[b=Newnode(k+1,R[t],v[t])]=rc[t],rc[t]=0,R[t]=k,a=t;
}
void calc(int &t){if(!t) return; cnt[v[t]]+=R[t]-L[t]+1,calc(lc[t]),calc(rc[t]),bin[++top]=t,t=0;}
void getans(int t){if(!t) return; for(int i=L[t];i<=R[t];i++) s[i]=v[t]+'a'; getans(lc[t]),getans(rc[t]);}
int main()
{
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
scanf("%d%d%s",&n,&m,s+1);
for(int i=1;i<=n;i++) merge(rt,rt,Newnode(i,i,s[i]-'a'));
int x,y,op,a,b,c;
while(m--){
scanf("%d%d%d",&x,&y,&op),split(rt,a,b,y),split(a,a,c,x-1);
for(int i=0;i<26;i++) cnt[i]=0;
calc(c);
if(op==1) {for(int i=0,p=x;i<26;i++) if(cnt[i]) merge(a,a,Newnode(p,p+cnt[i]-1,i)),p+=cnt[i];}
else {for(int i=25,p=x;i>=0;i--) if(cnt[i]) merge(a,a,Newnode(p,p+cnt[i]-1,i)),p+=cnt[i];}
merge(rt,a,b);
}
getans(rt);
puts(s+1);
}