2018.06.28 BZOJ1014 [JSOI2008]火星人prefix(非旋treap+hash)

时间:2022-05-15 15:26:20

[JSOI2008]火星人prefix

Time Limit: 10 Sec Memory Limit: 162 MB

Submit: 8951 Solved: 2860

Description

  火星人最近研究了一种操作:求一个字串两个后缀的公共前缀。比方说,有这样一个字符串:madamimadam,我们将这个字符串的各个字符予以标号:序号: 1 2 3 4 5 6 7 8 9 10 11 字符 m a d a m i m a d a m 现在,火星人定义了一个函数LCQ(x, y),表示:该字符串中第x个字符开始的字串,与该字符串中第y个字符开始的字串,两个字串的公共前缀的长度。比方说,LCQ(1, 7) = 5, LCQ(2, 10) = 1, LCQ(4, 7) = 0 在研究LCQ函数的过程中,火星人发现了这样的一个关联:如果把该字符串的所有后缀排好序,就可以很快地求出LCQ函数的值;同样,如果求出了LCQ函数的值,也可以很快地将该字符串的后缀排好序。 尽管火星人聪明地找到了求取LCQ函数的快速算法,但不甘心认输的地球人又给火星人出了个难题:在求取LCQ函数的同时,还可以改变字符串本身。具体地说,可以更改字符串中某一个字符的值,也可以在字符串中的某一个位置插入一个字符。地球人想考验一下,在如此复杂的问题中,火星人是否还能够做到很快地求取LCQ函数的值。

Input

  第一行给出初始的字符串。第二行是一个非负整数M,表示操作的个数。接下来的M行,每行描述一个操作。操作有3种,如下所示1、询问。语法:Qxy,x,y均为正整数。功能:计算LCQ(x,y)限制:1<=x,y<=当前字符串长度。2、修改。语法:Rxd,x是正整数,d是字符。功能:将字符串中第x个数修改为字符d。限制:x不超过当前字符串长度。3、插入:语法:Ixd,x是非负整数,d是字符。功能:在字符串第x个字符之后插入字符d,如果x=0,则在字符串开头插入。限制:x不超过当前字符串长度

Output

  对于输入文件中每一个询问操作,你都应该输出对应的答案。一个答案一行。

Sample Input

madamimadam

7

Q 1 7

Q 4 8

Q 10 11

R 3 a

Q 1 7

I 10 a

Q 2 11

Sample Output

5

1

0

2

1

HINT

1、所有字符串自始至终都只有小写字母构成。

2、M<=150,000

3、字符串长度L自始至终都满足L<=100,000

4、询问操作的个数不超过10,000个。

对于第1,2个数据,字符串长度自始至终都不超过1,000

对于第3,4,5个数据,没有插入操作。

题意简述:维护一个字符串中任意两段后缀的最长前缀,支持修改和插入操作。

我们先来思考一下静态版本,如果让我们维护字符串中任意两段后缀的最长前缀,怎么做?后缀数组?

其实我们可以用更简单的二分+哈希的方法来替代,尽管二分+哈希的效率不如前者优秀,但是码量少,思维难度小,并且实际测试并不慢。

动态版本?

但当我们看到插入操作时,不难联想到这是一道平衡树的题目,怎么维护?

如果我们按每个字符在序列中的位置为序建立这棵平衡树来维护区间的哈希值的话,每次只需提取出(ql,n)(ql,n)(ql,n)和(qr,n)(qr,n)(qr,n)来二分答案即可。

代码如下

#include<bits/stdc++.h>
#define M 1000010
#define bas 37
using namespace std;
typedef pair<int,int> res;
unsigned int hash[M],po[M];
int q,son[M][2],siz[M],v[M],rd[M],tot=0,root=0;
inline void pushup(int p){
	siz[p]=siz[son[p][0]]+siz[son[p][1]]+1;
	hash[p]=hash[son[p][0]]+v[p]*po[siz[son[p][0]]]+hash[son[p][1]]*po[siz[son[p][0]]+1];
}
inline int merge(int a,int b){
	if(!a||!b)return a+b;
	if(rd[a]<rd[b]){son[a][1]=merge(son[a][1],b),pushup(a);return a;}
	son[b][0]=merge(a,son[b][0]),pushup(b);return b;
}
inline res split(int a,int k){
	if(!a)return res(0,0);
	res ans,tmp;
	if(siz[son[a][0]]>=k){
		tmp=split(son[a][0],k);
		son[a][0]=tmp.second,pushup(a);
		ans.first=tmp.first,ans.second=a;
		return ans;
	}
	tmp=split(son[a][1],k-siz[son[a][0]]-1);
	son[a][1]=tmp.first,pushup(a);
	ans.first=a,ans.second=tmp.second;
	return ans;
}
inline int build(int val){
	int ret=++tot;
	hash[tot]=v[tot]=val;
	son[tot][0]=son[tot][1]=0;
	rd[tot]=rand();
	siz[tot]=1;
	return tot;
}
inline void insert(int pos,int v){
	res tmp=split(root,pos);
	int p=build(v);
	root=merge(merge(tmp.first,p),tmp.second);
}
inline void erase(int pos){
	res x=split(root,pos);
	res y=split(x.first,pos-1);
	root=merge(y.first,x.second);
}
char s[150005];
inline int ask(int p,int len){
	if(p+len-1>siz[root])return -1;
	res x=split(root,p-1);
	res y=split(x.second,len);
	int ret=hash[y.first];
	root=merge(x.first,merge(y.first,y.second));
	return ret;
}
inline void query(int ql,int qr){
	int l=0,r=siz[root]-qr+1,ans=0;
	while(l<=r){
		int mid=l+r>>1;
		int ax=ask(ql,mid),ay=ask(qr,mid);
		if(ax==-1||ay==-1){r=mid-1;continue;}
		if(ax==ay)ans=mid,l=mid+1;
		else r=mid-1;
	}
	printf("%d\n",ans);
}
int main(){
	po[0]=1;
	for(int i=1;i<=100000;++i)po[i]=po[i-1]*bas;
	scanf("%s",s+1);
	int len=strlen(s+1),l,r;
	for(int i=1;i<=len;++i)root=merge(root,build(s[i]-'a'));
	scanf("%d",&q);
	while(q--){
		char op[3],c[3];
		scanf("%s",op);
		if(op[0]=='Q'){
			scanf("%d%d",&l,&r);
			query(l,r);
		}
		if(op[0]=='R'){
			scanf("%d%s",&l,c);
			erase(l);
			insert(l-1,c[0]-'a');
		}
		if(op[0]=='I'){
			scanf("%d%s",&l,&c);
			insert(l,c[0]-'a');
		}
	}
	return 0;
}