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;
}