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

题目链接:BZOJ - 1014

题目分析

求两个串的 LCP ,一种常见的方法就是 二分+Hash,对于一个二分的长度 l,如果两个串的长度为 l 的前缀的Hash相等,就认为他们相等。

这里有修改字符和插入字符的操作,所以用 Splay 来维护串的 Hash 值。

一个节点的值就是它的子树表示的字串的 Hash 值。

使用 unsigned long long 然后自然溢出就不需要 mod 了,速度会快很多。

代码

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>

using namespace std;

inline int gmax(int a, int b) {return a > b ? a : b;}

typedef unsigned long long ULL;

const ULL Seed = 211;

const int MaxN = 100000 + 5;

int n, l, m, Root, Index;
int Father[MaxN], Son[MaxN][2], Size[MaxN];

ULL H[MaxN], Pow_Seed[MaxN]; 

char Str[MaxN], T[MaxN];

int NewNode(char c)
{
	int x = ++Index;
	T[x] = c;
	H[x] = (ULL)c;
	Size[x] = 1;
	return x;
}

inline void Update(int x)
{
	Size[x] = Size[Son[x][0]] + Size[Son[x][1]] + 1;
	H[x] = (H[Son[x][0]] * Seed + T[x]) * Pow_Seed[Size[Son[x][1]]] + H[Son[x][1]];
}

int Build(int s, int t)
{
	int x, m = (s + t) >> 1;
	x = NewNode(Str[m]);
	if (s < m)
	{
		Son[x][0] = Build(s, m - 1);
		Father[Son[x][0]] = x;
	}
	if (t > m)
	{
		Son[x][1] = Build(m + 1, t);
		Father[Son[x][1]] = x;
	}
	Update(x);
	return x;
}

inline int GetDir(int x)
{
	if (x == Son[Father[x]][0]) return 0;
	else return 1;
}

void Rotate(int x)
{
	int y = Father[x], f = GetDir(x) ^ 1;
	Father[x] = Father[y];
	if (Father[y])
	{
		if (y == Son[Father[y]][0]) Son[Father[y]][0] = x;
		else Son[Father[y]][1] = x;
	}
	Son[y][f ^ 1] = Son[x][f];
	if (Son[x][f]) Father[Son[x][f]] = y;
	Son[x][f] = y;
	Father[y] = x;
	Update(y); Update(x);
}

void Splay(int x, int d)
{
	int y;
	while (Father[x] != d)
	{
		y = Father[x];
		if (Father[y] == d)
		{
			Rotate(x);
			break;
		}
		if (GetDir(y) == GetDir(x)) Rotate(y);
		else Rotate(x);
		Rotate(x);
	}
	if (Father[x] == 0) Root = x;
}

int Find(int Num)
{
	int x = Root, k = Num;
	while (Size[Son[x][0]] + 1 != k)
	{
		if (Size[Son[x][0]] + 1 > k)
			x = Son[x][0];
		else
		{
			k -= Size[Son[x][0]] + 1;
			x = Son[x][1];
		}
	}
	return x;
}

bool Check(int p, int q, int len)
{
	if (len == 0) return true;
	int x, y;
	ULL Hp, Hq;
	x = Find(p);
	y = Find(p + len + 1);
	Splay(x, 0);
	Splay(y, x);
	Hp = H[Son[y][0]];
	x = Find(q);
	y = Find(q + len + 1);
	Splay(x, 0);
	Splay(y, x);
	Hq = H[Son[y][0]];

	return Hp == Hq;
}

int main()
{
	Pow_Seed[0] = 1;
	for (int i = 1; i <= 100000; ++i)
		Pow_Seed[i] = Pow_Seed[i - 1] * Seed;
	scanf("%s", Str + 1);
	l = strlen(Str + 1);
	n = l;
	Root = Build(0, l + 1);
	scanf("%d", &m);
	char f, ch;
	int Pos, x, y, p, q, l, r, mid, Ans;
	for (int i = 1; i <= m; ++i)
	{
		f = ‘-‘;
		while (f < ‘A‘ || f > ‘Z‘) f = getchar();
		switch (f)
		{
			case ‘R‘ :
				scanf("%d %c", &Pos, &ch);
				x = Find(Pos + 1);
				Splay(x, 0);
				T[x] = ch;
				H[x] = (ULL)ch;
				Update(x);
				break;

			case ‘I‘ :
				++n;
				scanf("%d %c", &Pos, &ch);
				x = Find(Pos + 1);
				y = Find(Pos + 2);
				Splay(x, 0);
				Splay(y, x);
				Son[y][0] = ++Index;
				T[Index] = ch;
				H[Index] = (ULL)ch;
				Size[Index] = 1;
				Father[Index] = y;
				Update(y); Update(x);
				break;

			case ‘Q‘ :
				scanf("%d%d", &p, &q);
				l = 0; r = n - gmax(p, q) + 1;
				while (l <= r)
				{
					mid = (l + r) >> 1;
					if (Check(p, q, mid))
					{
						Ans = mid;
						l = mid + 1;
					}
					else r = mid - 1;
				}
				printf("%d\n", Ans);
				break;
		}
	}
	return 0;
}

  

时间: 2024-12-16 19:20:43

[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

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

题目大意:给定一个字符串,提供下列操作: 1.查询从x开始的后缀和从y开始的后缀的最长公共前缀长度 2.将x位置的字符修改为y 3.在x位置的字符后面插入字符y 看到这题一开始我先懵住了...这啥..我第一时间想到的是后缀数据结构 但是不会写 而且后缀数据结构也不支持修改操作 后来无奈找了题解才知道是Hash+二分... 太强大了 Hash+二分打爆一切啊 用Splay维护这个字符串的修改和插入操作 每个节点维护子串的Hash值 判断时二分找到最长公共前缀 不过这道题还有一些注意事项 1.此题不

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

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

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

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

【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)

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(splay+hash)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1014 [题意] 给定一个字符串,要求提供修改一个字符,插入一个字符,查询两个后缀LCP的功能. [思路]  splay维护字符串的哈希值.因为要提供区间,splay采用先查找后调整至根的写法. 一个结点的hash值为: ch[0]->h * X^(ch[1]->s+1)+v * X^(ch[1]->s)+ch[1]->h     对于一个询问每次二分长度,提取区间后比较