BZOJ1014 [JSOI2008]火星人prefix(Splay+字符串Hash)

【题解】

动态的LCP问题 

用 Splay 处理动态区间:

    插入操作"I x d"的实现:
    首先将x旋转至树根,则d应插在x的右字树中
    找到 x的右子树的最左端结点(即原来的s[x+1]在树中的对应结点),将d添加为它的左孩子 

用 字符串Hash 判断字符串是否相等:

    o->H表示由o及其左右子树所对应字母构成的字符串的Hash值,
    则 o->H = o->ch[0]->H + o->s*xp[o->ch[0]->size] + o->ch[1]->H*xp[o->ch[0]->size+1]

    求一段区间[a,a+L-1]的Hash(s[a]~s[a+L-1]):
    将s[a-1]旋转至根,则root->ch[1]->H为s[a]的后缀Hash值(在序列最左端添加"哨兵",防止a-1==0),记为ha1
    将s[a+L-1]旋转至根,则root->ch[1]->H为s[a+L]的后缀Hash值,记为ha2
    Hash(s[a]~s[a+L-1])=ha1 - ha2*(X^p)

求LCP,即"Q x y":

    若s[x]==s[y],二分查找长度L,使Hash( s[x]~s[x+L-1] )==Hash( s[y]~s[y+L-1] )

注意:
1. 序列长度并非任何时候都是n,因此在预处理xp数组时,应从1循环到10^5; 二分答案时用root->s表示长度,不能随便试用n
2. 警惕手抖将ch[0]打成ch[d]等等 


【代码】

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define X 23ll
typedef unsigned long long ULL;
ULL xp[100010];
char s[100010];
struct Node
{
	Node* ch[2];
	ULL H;
	int v,s;
	int cmp(int x) const
	{
		if( x == ch[0]->s + 1 )return -1;
		if( x <= ch[0]->s ) return 0;
		return 1;
	}
};
Node *root,*null;
int max(int a,int b)
{
	if(a>b) return a;
	return b;
}
void init()
{
	null=new Node();
	null->ch[0]=null->ch[1]=NULL;
	null->H=0;
	null->v=null->s=0;
	root=null;
}
void wh(Node* &o)
{
	o->s = o->ch[0]->s + 1 + o->ch[1]->s;
	o->H = o->ch[0]->H + (ULL)o->v*xp[o->ch[0]->s] + o->ch[1]->H*xp[o->ch[0]->s+1];
}
void build(Node* &o,int left,int right)
{
	int mid=(left+right)/2;
	o=new Node();
	o->ch[0]=o->ch[1]=null;
	o->v=s[mid];
	if(left<mid) build(o->ch[0],left,mid-1);
	if(right>mid) build(o->ch[1],mid+1,right);
	wh(o);
}
void xz(Node* &o,int d)
{
	Node* k=o->ch[d^1];
	o->ch[d^1]=k->ch[d];
	k->ch[d]=o;
	wh(o);
	wh(k);
	o=k;
}
void splay(Node* &o,int k)
{
	int d=o->cmp(k),d2,k2;
	if(d==1) k -= o->ch[0]->s + 1;
	if(d!=-1)
	{
		Node* p=o->ch[d];
		k2=k;
		d2=p->cmp(k2);
		if(d2==1) k2 -= p->ch[0]->s + 1;
		if(d2!=-1)
		{
			splay(p->ch[d2],k2);
			if(d==d2) xz(o,d^1);
			else xz(o->ch[d],d2^1);
		}
		xz(o,d^1);
	}
}
int possible(int x,int y,int L)
{
	ULL h1,h2;
	if(L==0) return 1;
	splay(root,x-1);
	h1=root->ch[1]->H;
	splay(root,x+L-1);
	h1-=root->ch[1]->H*xp[L];
	splay(root,y-1);
	h2=root->ch[1]->H;
	splay(root,y+L-1);
	h2-=root->ch[1]->H*xp[L];
	return h1==h2;
}
int cx(int x,int y)
{
	int left=0,right=root->s-max(x,y)+1,mid;
	while(left<right)
	{
		mid=(left+right+1)/2;
		if(possible(x,y,mid)) left=mid;
		else right=mid-1;
	}
	return left;
}
void tj(Node* &o,int x)
{
	if(o==null)
	{
		o=new Node();
		o->ch[0]=o->ch[1]=null;
		o->v=x;
		o->H=(ULL)x;
		o->s=1;
		return;
	}
	tj(o->ch[0],x);
	wh(o);
}
int main()
{
	char opt,d;
	int n,m,i,x,y;
	init();
	scanf("%s%d",s,&m);
	n=strlen(s);
	for(i=n;i>=1;i--)
		s[i]=s[i-1];
	s[0]=0;
	xp[0]=1LL;
	for(i=1;i<=100005;i++)
		xp[i]=X*xp[i-1];
	build(root,0,n);
	for(;m>0;m--)
	{
		scanf("\n%c",&opt);
		if(opt=='Q')
		{
			scanf("%d%d",&x,&y);
			printf("%d\n",cx(x+1,y+1));
		}
		if(opt=='R')
		{
			scanf("%d %c",&x,&d);
			splay(root,x+1);
			root->v=d;
			wh(root);
		}
		if(opt=='I')
		{
			scanf("%d %c",&x,&d);
			splay(root,x+1);
			tj(root->ch[1],d);
			wh(root);
		}
	}
	return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值