传送门
后缀平衡树模板,它适用于如下问题:
①支持在前端插入一个字符
②询问当前字符串所有后缀的字典序大小关系
一个显然的想法是哈希+二分,然后询问用线段树维护一下。然而
l
o
g
2
log^2
log2要
T
T
T掉。
假设已经维护好当前串的所有后缀大小关系,现在要从前方插入一个字符。
发现在前端插入一个字符时,只会产生一个新的后缀。而比较它与其它后缀的字典序大小时,一定会先比较它们的第一个字符,如果不同就直接比出来了。如果相同就比较后面的字符——这是已经维护好了的。于是比较变成
O
(
1
)
O(1)
O(1)的了。
这个东西可以用平衡树来维护,由于我们想要比较的只是相对大小,于是一个奇妙的 t r i c k trick trick:给每个节点赋一个值域: [ l , r ] [l,r] [l,r],再令每个节点的“绝对排名”为: m i d = l + r 2.0 mid=\frac{l+r}{2.0} mid=2.0l+r,左儿子值域为 [ l , m i d ] [l,mid] [l,mid],右儿子值域为 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]。
这样之后,一个节点左子树中节点的“绝对排名”都一定小于当前节点排名,右子树中节点的“绝对排名”都一定大于当前节点排名。那么在比较节点的排名大小时,只用比较它们的“绝对排名”即可。于是不用在树上求 r a n k rank rank了。
然而如 s p l a y splay splay这种带有旋转操作的平衡树,如果要旋转,值域的关系就会乱掉,要想继续维护就得重构,不可取。
于是采用替罪羊树这种不带旋的平衡树维护即可。平衡树上的标号即为字符串中对应的位置(字符串从右往左标号依次增大)。
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+10,maxl=9e5+10;
char s[maxl],op[2];
int P[maxn],n,m,len,lastans=0;
namespace SGP{
int lc[maxl],rc[maxl],siz[maxl],root,*bad;
double L[maxl],R[maxl];
const double alpha=0.8;
inline double val(int x){if(!x) return -1e18;return L[x]+R[x];}
inline bool cmp(int a,int b){
return s[a]<s[b]||((s[a]==s[b])&&val(a-1)<val(b-1));
}
inline void ins(int &u,int i,double l=-1e8,double r=1e8){
if(!u){u=i,L[i]=l,R[i]=r,siz[i]=1;return;}
double mid=(l+r)/2;++siz[u];
if(cmp(u,i)){
ins(rc[u],i,mid,r);
if(siz[rc[u]]>siz[u]*alpha) bad=&u;
}
else{
ins(lc[u],i,l,mid);
if(siz[lc[u]]>siz[u]*alpha) bad=&u;
}
}
int st[maxl],top=0;
inline void inorder_dfs(int u){
if(lc[u])inorder_dfs(lc[u]);st[++top]=u;
if(rc[u])inorder_dfs(rc[u]);
}
inline int build(int l,int r,double vl,double vr){
if(l>r) return 0;int mid=l+r>>1;double Mid=(vl+vr)/2;
int u=st[mid];L[u]=vl,R[u]=vr,siz[u]=r-l+1;
lc[u]=build(l,mid-1,vl,Mid);
rc[u]=build(mid+1,r,Mid,vr);
return u;
}
inline void rebuild(int &k){
top=0;inorder_dfs(k);
k=build(1,top,L[k],R[k]);
}
inline void insert(int i){
bad=NULL;ins(root,i);
if(bad!=NULL) rebuild(*bad);
}
}
namespace SGT{
#define lc (root<<1)
#define rc (root<<1|1)
#define mid (T[root].l+T[root].r>>1)
struct node{int l,r,ans;}T[maxn<<2];
inline int getmn(int a,int b){
return (P[a]!=P[b]?SGP::val(P[a])<SGP::val(P[b]):a<b)?a:b;
}
inline void pushup(int root){T[root].ans=getmn(T[lc].ans,T[rc].ans);}
inline void build(int root,int l,int r){
T[root].l=l,T[root].r=r;
if(l==r){T[root].ans=l;return;}
build(lc,l,mid),build(rc,mid+1,r),pushup(root);
}
inline void update(int root,int x){
if(T[root].l==T[root].r){T[root].ans=T[root].l;return;}
if(x<=mid) update(lc,x);
else update(rc,x);
pushup(root);
}
inline int query(int root,int l,int r){
if(l<=T[root].l&&T[root].r<=r) return T[root].ans;
if(l> mid) return query(rc,l,r);
if(r<=mid) return query(lc,l,r);
return getmn(query(lc,l,mid),query(rc,mid+1,r));
}
#undef lc
#undef rc
#undef mid
}
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x;
}
int c,x,pos,l,r,type;
int main(){
//freopen("phorni.in","r",stdin);
n=read(),m=read(),len=read(),type=read(),scanf("%s",s+1);
reverse(s+1,s+len+1);
for(int i=1;i<=len;++i) SGP::insert(i);
for(int i=1;i<=n;++i) P[i]=read();
SGT::build(1,1,n);
while(m--){
scanf("%s",op);
if(op[0]=='I') c=read()^(lastans*type),s[++len]='a'+c,SGP::insert(len);
if(op[0]=='C') x=read(),pos=read(),P[x]=pos,SGT::update(1,x);
if(op[0]=='Q') l=read(),r=read(),printf("%d\n",lastans=SGT::query(1,l,r));
}
}