bzoj3682 Phorni

传送门
后缀平衡树模板,它适用于如下问题:
①支持在前端插入一个字符
②询问当前字符串所有后缀的字典序大小关系

一个显然的想法是哈希+二分,然后询问用线段树维护一下。然而 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));
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值