BZOJ 1014 JSOI2008 火星人prefix Splay+Hash+二分

题目大意:给定一个字符串,提供下列操作:

1.查询从x开始的后缀和从y开始的后缀的最长公共前缀长度

2.将x位置的字符修改为y

3.在x位置的字符后面插入字符y

看到这题一开始我先懵住了。。。这啥。。我第一时间想到的是后缀数据结构 但是不会写 而且后缀数据结构也不支持修改操作

后来无奈找了题解才知道是Hash+二分。。。 太强大了 Hash+二分打爆一切啊

用Splay维护这个字符串的修改和插入操作 每个节点维护子串的Hash值 判断时二分找到最长公共前缀

不过这道题还有一些注意事项

1.此题不卡Hash 不会出现ABBABAABBAABABBA之类恶心字符串

2.题目没有明说 但是字符串上所有字符都是小写字母

3.二分时注意上界的取值 不能超过字符串长度 此题不保证x<y 一定要特判 否则就会RE

剩下就很裸了、、、80%达成 可以补个番了0.0

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 100100
using namespace std;
typedef unsigned long long ll;
struct abcd{
	abcd *fa,*ls,*rs;
	int num,siz;ll hash;
	abcd(int x);
	void Push_Up();
}*null=new abcd(0),*root=null;
abcd :: abcd(int x)
{
	fa=ls=rs=null;
	num=x;siz=x?1:0;hash=x;
}
ll ksm[M];
void abcd :: Push_Up()
{
	siz=ls->siz+rs->siz+1;
	hash=ls->hash*ksm[rs->siz+1]+num*ksm[rs->siz]+rs->hash;
}
void Zig(abcd *x)
{
	abcd *y=x->fa;
	y->ls=x->rs;
	x->rs->fa=y;
	x->rs=y;
	x->fa=y->fa;
	if(y==y->fa->ls)
		y->fa->ls=x;
	else if(y==y->fa->rs)
		y->fa->rs=x;
	y->fa=x;
	y->Push_Up();
	if(y==root)
		root=x;
}
void Zag(abcd *x)
{
	abcd *y=x->fa;
	y->rs=x->ls;
	x->ls->fa=y;
	x->ls=y;
	x->fa=y->fa;
	if(y==y->fa->ls)
		y->fa->ls=x;
	else if(y==y->fa->rs)
		y->fa->rs=x;
	y->fa=x;
	y->Push_Up();
	if(y==root)
		root=x;
}
void Splay(abcd *x,abcd *Tar)
{
	while(1)
	{
		abcd *y=x->fa,*z=y->fa;
		if(y==Tar)
			break;
		if(z==Tar)
		{
			if(x==y->ls)
				Zig(x);
			else
				Zag(x);
			break;
		}
		if(x==y->ls)
		{
			if(y==z->ls)
				Zig(y);
			Zig(x);
		}
		else
		{
			if(y==z->rs)
				Zag(y);
			Zag(x);
		}
	}
	x->Push_Up();
}
void Find(abcd *x,int y,abcd *z)
{
	while(1)
	{
		if(y<=x->ls->siz)
			x=x->ls;
		else
		{
			y-=x->ls->siz;
			if(y==1)
				break;
			y--;
			x=x->rs;
		}
	}
	Splay(x,z);
}
char s[M];
void Build_Tree(abcd *&x,int l,int r)
{
	if(l>r)
		return ;
	int mid=l+r>>1;
	x=new abcd(s[mid]-'a'+1);
	Build_Tree(x->ls,l,mid-1);
	Build_Tree(x->rs,mid+1,r);
	x->ls->fa=x;
	x->rs->fa=x;
	x->Push_Up();
}
void Initialize()
{
	int i;
	ksm[0]=1;
	for(i=1;i<=100010;i++)
		ksm[i]=ksm[i-1]*31;
	scanf("%s",s);
	root=new abcd(19980402);
	root->rs=new abcd(19980402);
	Build_Tree(root->rs->ls,0,strlen(s)-1);
	root->rs->ls->fa=root->rs;
	root->rs->fa=root;
	root->rs->Push_Up();
	root->Push_Up();
}
bool Judge(int x,int y,int ans)
{
	Find(root,x,null);
	Find(root,x+ans+1,root);
	ll hash1=root->rs->ls->hash;
	Find(root,y,null);
	Find(root,y+ans+1,root);
	ll hash2=root->rs->ls->hash;
	return hash1==hash2;
}
int Query(int x,int y)
{
	int l=0,r=root->siz-2-max(x,y)+1;
	while(l+1<r)
	{
		int mid=l+r>>1;
		if( Judge(x,y,mid) )
			l=mid;
		else
			r=mid;
	}
	if( Judge(x,y,r) )
		return r;
	return l;

}
int m;
int main()
{
	int i,x,y;
	char p[10];
	Initialize();
	cin>>m;
	for(i=1;i<=m;i++)
	{
		scanf("%s",p);
		if(p[0]=='R')
		{
			scanf("%d%s",&x,p);
			Find(root,x+1,null);
			root->num=p[0]-'a'+1;
			root->Push_Up();
		}
		else if(p[0]=='I')
		{
			scanf("%d%s",&x,p);
			Find(root,x+1,null);
			Find(root,x+2,root);
			root->rs->ls=new abcd(p[0]-'a'+1);
			root->rs->ls->fa=root->rs;
			root->rs->Push_Up();
			root->Push_Up();
		}
		else
			scanf("%d%d",&x,&y),printf("%d\n", Query(x,y) );
	}
}
时间: 2024-10-06 21:29:52

BZOJ 1014 JSOI2008 火星人prefix Splay+Hash+二分的相关文章

BZOJ 1014: [JSOI2008]火星人prefix( splay + hash )

用splay维护序列, 二分+hash来判断LCQ.. #include<bits/stdc++.h> using namespace std; typedef unsigned long long ull; const int maxn = 100009; const int P = 1000173169; ull K[maxn]; int N; char S[maxn]; struct Node { Node *ch[2], *p; int s, v; ull h; inline void

【bzoj1014】[JSOI2008]火星人prefix Splay+Hash+二分

题目描述 火星人最近研究了一种操作:求一个字串两个后缀的公共前缀.比方说,有这样一个字符串: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

bzoj 1014 [JSOI2008]火星人prefix (splay+二分答案+字符串hash)

题目大意:维护一个字符串,支持插入字符和替换字符的操作,以及查询该字符串两个后缀的最长公共前缀长度 乍一看以为是后缀数组,然而并没有可持久化后缀数组(雾) 看题解才知道这是一道splay题,首先要对splay维护区间信息有一定了解 splay维护,插入字符,替换字符 而它的字树内所有儿子的中序遍历的hash值也可以通过splay维护  (这个推导式似乎烂大街了) 而后缀就是把i-1拎到根节点,然后把n+1拎到根节点的右儿子上,它的左儿子表示的就是hash值 至于如何查公共前缀呢?二分答案啊!询问

[BZOJ1014][JSOI2008]火星人prefix splay+hash+二分

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1014 先考虑如果没有修改操作和插入操作,是一个静态的字符串,我们可以怎样快速求得题目中的LCQ. 两个字符串判等很容易想到hash.于是我们二分答案并二分判断,就可以在$log_n$时间内得到答案. 现在加上修改和插入操作,其实就是要动态维护子串也就是一段区间的hash值,这种问题很容易就想到用splay来维护. 每个节点记录此节点管辖下子树的hash值h,当前节点的h=左孩子的h+节点

bzoj 1014: [JSOI2008]火星人prefix hash &amp;&amp; splay

1014: [JSOI2008]火星人prefix Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 3154  Solved: 948[Submit][Status] Description 火星人最近研究了一种操作:求一个字串两个后缀的公共前缀.比方说,有这样一个字符串:madamimadam,我们将这个字符串的各个字符予以标号:序号: 1 2 3 4 5 6 7 8 9 10 11 字符 m a d a m i m a d a m 现在,火星人

求帮看!!!!BZOJ 1014 [JSOI2008]火星人prefix

1014: [JSOI2008]火星人prefix Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 4164  Solved: 1277[Submit][Status][Discuss] Description 火星人最近研究了一种操作:求一个字串两个后缀的公共前缀.比方说,有这样一个字符串:madamimadam,我们将这个字符串的各个字符予以标号:序号: 1 2 3 4 5 6 7 8 9 10 11 字符 m a d a m i m a d

[BZOJ 1014] [JSOI2008] 火星人prefix 【Splay + Hash】

题目链接:BZOJ - 1014 题目分析 求两个串的 LCP ,一种常见的方法就是 二分+Hash,对于一个二分的长度 l,如果两个串的长度为 l 的前缀的Hash相等,就认为他们相等. 这里有修改字符和插入字符的操作,所以用 Splay 来维护串的 Hash 值. 一个节点的值就是它的子树表示的字串的 Hash 值. 使用 unsigned long long 然后自然溢出就不需要 mod 了,速度会快很多. 代码 #include <iostream> #include <cstd

[BZOJ 1014][JSOI2008]火星人prefix(Splay+二分+hash)

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(

bzoj 1014[JSOI2008]火星人prefix - 二分 + hash + splay

我们发现要支持修改操作,所以后缀数组就不适用了 查询两个字符串的lcp有两个很常见的算法, 后缀数组和 二分哈希 所以对于字符串的修改我们用一个splay 来维护, 平衡树每个节点表示的是对应子树的字符串的哈希值. 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #define LL long long 6 using namesp