P4036 [JSOI2008]火星人

题意描述

洛谷

给你一个字符串,让你支持三个操作。

  • 操作一:查询当前字符串以 \(x\) 为开头的后缀和以 \(y\) 为开头的后缀的 \(lcp\)
  • 操作二:将当前字符串的第 \(x\) 个字符改为 \(ch\)
  • 操作三:在当前字符串的第 \(x\) 个字符后面添加一个字符 \(c\)

数据范围: 操作数 \(m\leq 1.5\times 10^5\) , 字符串长度 \(n\leq 10^5\)

solution:

平衡树加哈希。

求两个字符串的 \(lcp\) 无非就两种方法:后缀数组和哈希。

如果说我们没有第二个和第三个操作的话,可以直接用后缀数组求解。

但维护操作二和操作三的话需要可持久化后缀数组(好像没有这个玩意)。

那我们只能用哈希来求两个字符串的 \(lcp\)

我们又要动态维护这个字符串,可以考虑用平衡树维护一下哈希。

具体操作如下:

对于每个·节点 \(i\),维护一个 \(sum[i]\) 数组表示中序遍历这个节点子树所形成的字符串 \(s[i]\) 的哈希值。

同时维护两个数组 \(siz[i]\)\(ch[i]\) 表示子树的大小,和 \(i\) 号点所代表的的字符。

对于字符串 \(s[i]\) 显然是由 \(s[son[i][0]] + ch[i] + s[son[i][1]]\) 拼接起来的。

根据字符串哈希那套理论可得:

\(\large sum[i] = sum[son[i][0]] \times p^{siz[son[i][1]]+1} + ch[i] \times p^{siz[son[i][1]]} + sum[son[i][1]]\)

求当前字符串的第 \(l\) 个字符到第 \(r\) 个字符所组成的字符串的哈希值时,设 \(x = rk(l-1), y = rk(r+1)\)

把 节点 \(x\) 旋转到根, \(y\) 旋转到 \(x\) 的儿子上,\(sum[y][0]\) 即为所求。

操作二和操作三就是平衡树的插入和修改操作,随便维护一下就行。

最后注意写的时候要注意各种小细节,不然又需要调很长时间。

Code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define ull unsigned long long
const int p = 13131;
const int N = 2e5+10;
int n,m,tot,root,x,y;
int siz[N],fa[N],son[N][2];
ull sum[N],val[N],w[N],base[N];
char ch,opt,a[N];
inline int read()
{
    int s = 0, w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    return s * w;
}
void up(int o)
{
	siz[o] = siz[son[o][0]] + siz[son[o][1]] + 1;
	sum[o] = sum[son[o][0]] * base[siz[son[o][1]]+1] + val[o] * base[siz[son[o][1]]] + sum[son[o][1]];
}
bool ispant(int x)
{
	return son[fa[x]][0] == x ? 0 : 1;
}
void build(int &o,int f,int l,int r)
{
	if(l > r) return;
	o = ++tot;
	int mid = (l+r)>>1;
	sum[o] = val[o] = w[mid]; siz[o] = 1; fa[o] = f;
	build(son[o][0],o,l,mid-1);
	build(son[o][1],o,mid+1,r);
	up(o);
}
void retate(int x)
{
	int y = fa[x], z = fa[y];
	int px = ispant(x), py = ispant(y);
	son[y][px] = son[x][px^1];
	if(son[x][px^1]) fa[son[x][px^1]] = y;
	son[x][px^1] = y;
	fa[y] = x;
	if(z) son[z][py] = x;
	fa[x] = z;
	up(y); up(x);
}
void splay(int x,int to)
{
	while(fa[x] != to)
	{
		int y = fa[x], z = fa[y];
		int px = ispant(x), py = ispant(y);
		if(z == to) retate(x);
		else
		{
			px == py ? retate(y) : retate(x);
			retate(x);
		}
		up(y); up(x);
	}
	if(to == 0) root = x;
}
int kth(int k)
{
	int o = root;
	while(1)
	{
		if(siz[son[o][0]] >= k) o = son[o][0];
		else if(siz[son[o][0]] + 1 == k) return o;
		else k -= siz[son[o][0]] + 1, o = son[o][1];
	}
}
ull get(int l,int r)//求当前字符串的第l个字符到第r个字符组成的字符串的哈希值
{
	int x = kth(l), y = kth(r+2);
	splay(x,0); splay(y,x);
	return sum[son[y][0]];
}
int lcp(int x,int y)//二分加hash求lcp
{
	int L = 1, R = min(n-x+1,n-y+1), ans = 0;
	while(L <= R)
	{
		int mid = (L + R)>>1;
		if(get(x,x+mid-1) == get(y,y+mid-1))
		{
			ans = mid;
			L = mid + 1;
		}
		else R = mid - 1;
	}
	return ans;
}
int main()
{
	scanf("%s",a+1);
	n = strlen(a+1); base[0] = 1;
	for(int i = 1; i <= 2*n; i++) base[i] = base[i-1] * p;
	w[0] = 0, w[n+1] = 0;
	for(int i = 1; i <= n; i++) w[i] = a[i] - 'a';
	build(root,0,0,n+1);
	m = read();
	for(int i = 1; i <= m; i++)
	{
		cin>>opt;
		if(opt == 'Q')
		{
			x = read(); y = read();
			printf("%d\n",lcp(x,y));
		}
		if(opt == 'R')
		{
			x = read(); cin>>ch;
			int l = kth(x), r = kth(x+2);//单点修改
			splay(l,0); splay(r,l);
			sum[son[r][0]] = val[son[r][0]] = ch - 'a';
			up(r); up(l);
		}
		if(opt == 'I')
		{
			x = read(); cin>>ch;
			int o = ++tot; n++;
			sum[o] = val[o] = ch - 'a'; siz[o] = 1;//插入一个数
			int l = kth(x+1), r = kth(x+2);
			splay(l,0); splay(r,l);
			son[r][0] = o; fa[o] = r;
			while(fa[o]) o = fa[o], up(o);
		}
	}
	return 0;
}

posted @ 2021-02-18 20:50  genshy  阅读(85)  评论(0编辑  收藏  举报