NOIP模拟赛20191023 T1 string【字符串排序,线段树/无旋Treap】

题目描述:

给定一个由小写字母组成的字符串 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);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值