https://www.luogu.com.cn/problem/P6292
题解
一道LCT+SAM的板子题。
考虑一个简化版的问题:区间数颜色。
显然可以离线后枚举右端点,然后求出左端点在 ([1,r]) 这个范围内,不同颜色的个数。我们考虑新加进来一个 (c),先对每个 (l) 都增加 (1),记 (c) 上一次出现的位置为 (lst_c),如果没有出现则为 (0)。那么在 ([1,lst_c]) 范围内的左端点都被算重,需要减少 (1)。使用数据结构维护即可。
这道题是类似的。
考虑增加了一个点,那么他会增加很多字符串。具体来说是其parent tree上的祖先的集合内的串。我们还是考虑维护一个 (lst) 代表每个串上一次出现的位置(这里串出现位置指右端点出现位置)。显然对于一个endpos等价类其 (lst) 永远相同,所以对于sam上每个状态维护 (lst) 即可。那么对于一次操作可以看成从根到该点全部赋值。然后考虑减去算重的部分,对于每个状态,在 ([lst-len+1,lst-len_{fa}]) 这个范围内的已经算过了,所以要减去 (1)。还是同样的维护方法。
这样直接暴力跳父亲的复杂度会被卡成 (O(n^2log{n}))。我们发现这个过程非常像LCT的Access操作,而对于 (lst) 相同的一条链其实可以看做一个点来处理。所以模拟access的即可。复杂度均摊 (O(nlog^2{n}+mlog{n}))。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=500005;
template <typename T>
void read(T &x) {
T flag=1;
char cha=getchar();
for (; '0'>cha||cha>'9'; cha=getchar()) if (cha=='-') flag=-1;
for (x=0; '0'<=cha&&cha<='9'; cha=getchar()) x=x*10+cha-'0';
x*=flag;
}
template <typename T>
void cmax(T &x, T y) { x=max(x, y); }
template <typename T>
void cmin(T &x, T y) { x=min(x, y); }
void file() {
#ifdef LOCAL
freopen("test.in", "r", stdin);
#endif
}
int n, m;
char s[MAXN];
int son[MAXN][26], len[MAXN], fail[MAXN], lst=1, tot=1;
void ins(int c) {
int p=lst;
int np=lst=++tot;
len[np]=len[p]+1;
for (; p&&!son[p][c]; p=fail[p]) son[p][c]=np;
if (!p) fail[np]=1;
else {
int q=son[p][c];
if (len[q]==len[p]+1) fail[np]=q;
else {
int nq=++tot;
for (int i=0; i<26; i++) son[nq][i]=son[q][i];
fail[nq]=fail[q];
len[nq]=len[p]+1;
fail[q]=fail[np]=nq;
for (; p&&son[p][c]==q; p=fail[p]) son[p][c]=nq;
}
}
}
ll sum[MAXN<<2], add[MAXN<<2];
void push_add(int p, int l, int r, int v) {
sum[p]+=1ll*(r-l+1)*v;
add[p]+=v;
}
void push_down(int p, int l, int r) {
if (add[p]) {
int mid=(l+r)>>1;
push_add(p<<1, l, mid, add[p]);
push_add(p<<1|1, mid+1, r, add[p]);
add[p]=0;
}
}
void push_up(int p) {
sum[p]=sum[p<<1]+sum[p<<1|1];
}
void change(int p, int l, int r, int x, int y, int v) {
if (l>y||r<x) return;
if (x<=l&&r<=y) {
push_add(p, l, r, v);
return;
}
push_down(p, l, r);
int mid=(l+r)>>1;
change(p<<1, l, mid, x, y, v);
change(p<<1|1, mid+1, r, x, y, v);
push_up(p);
}
ll ask(int p, int l, int r, int x, int y) {
if (l>y||r<x) return 0;
if (x<=l&&r<=y) return sum[p];
push_down(p, l, r);
int mid=(l+r)>>1;
return ask(p<<1, l, mid, x, y)+ask(p<<1|1, mid+1, r, x, y);
}
int fa[MAXN], ch[MAXN][2], val[MAXN], tag[MAXN];
void pushtag(int p, int v) {
val[p]=tag[p]=v;
}
void pushdown(int p) {
if (tag[p]) {
if (ch[p][0]) pushtag(ch[p][0], tag[p]);
if (ch[p][1]) pushtag(ch[p][1], tag[p]);
tag[p]=0;
}
}
int get(int x) {
return ch[fa[x]][1]==x;
}
bool root(int x) {
return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;
}
void update(int x) {
if (!root(x)) update(fa[x]);
pushdown(x);
}
void rotate(int x) {
int y=fa[x], z=fa[y], w=get(x);
if (!root(y)) ch[z][ch[z][1]==y]=x;
fa[x]=z;
ch[y][w]=ch[x][w^1]; fa[ch[x][w^1]]=y;
ch[x][w^1]=y; fa[y]=x;
}
void splay(int x) {
update(x);
for (int y=fa[x]; !root(x); rotate(x), y=fa[x]) if (!root(y)) rotate(get(x)==get(y)?y:x);
}
void access(int x, int v) {
int y;
change(1, 1, n, 1, v, 1);
for (y=0; x; y=x, x=fa[x]) {
splay(x); ch[x][1]=y;
if (val[x]) change(1, 1, n, val[x]-len[x]+1, val[x]-len[fa[x]], -1);
}
pushtag(y, v);
}
struct query{
int l, r, id;
friend bool operator < (query a, query b) {
if (a.r==b.r) return a.l<b.l;
return a.r<b.r;
}
}q[MAXN];
ll ans[MAXN];
int pos[MAXN];
int main() {
scanf("%s", s+1);
n=strlen(s+1);
for (int i=1; i<=n; i++) ins(s[i]-'a'), pos[i]=lst;
for (int i=2; i<=tot; i++) fa[i]=fail[i];
read(m);
for (int i=1; i<=m; i++) {
read(q[i].l); read(q[i].r); q[i].id=i;
}
sort(q+1, q+1+m);
int now=0;
for (int i=1; i<=m; i++) {
while (now<q[i].r) {
now++;
access(pos[now], now);
}
ans[q[i].id]=ask(1, 1, n, q[i].l, q[i].r);
}
for (int i=1; i<=m; i++) printf("%lld
", ans[i]);
return 0;
}