【题解】P6292 区间本质不同子串个数
思路
SAM + LCT.
子串计数大概率需要一个 SAM.
首先这个问题和区间数颜色很类似,回忆一下怎么数颜色。
一种常见的方法是考虑扫描线。令 \(p(i)\) 为第 \(i\) 种颜色最后一次出现的位置,则 \([l, r]\) 的答案为 \(\sum\limits [p(i) \geq l]\)。用数据结构动态维护就行。
这个题同样考虑扫描线,不过维护的是子串开头最后一次出现的位置。考虑把询问离线下来,从前往后加入字符。
加入下标 \(x\) 会导致 \([1, x]\) 的所有后缀最后出现的位置改变,由 parent tree 易知这里的后缀等价于 parent tree 上一条从根到某个结点的路径。
于是只需要考虑把这条路径原本的贡献去掉,再加入新的贡献。
这里的操作类似于 LCT 的 access,不妨用 LCT 维护 parent tree 的结构。一种暴力的思路是直接在 LCT 上暴力跳,但是复杂度是假的。
但是我们发现这条路径是连续的,所以可以在 access 的时候顺便做一下。
LCT 不能维护贡献,另外再搞一棵线段树,维护以每个位置为开头的子串数量。因为 parent tree 知,这里路径上的子串长度是连续的。在固定右端点的情况下,它们的开头显然也是一段连续的区间。所以只需要在线段树上大力修改就行。
时间复杂度 \(O(n \log^2 n + m \log n)\)
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
const int maxq = 2e5 + 5;
const int sam_sz = maxn << 1;
const int sgt_sz = maxn << 2;
int n, q;
int pos[maxn];
ll ans[maxq];
char s[maxn];
namespace SAM
{
int lst = 1, cur = 1;
int len[sam_sz], fa[sam_sz], son[sam_sz][26];
void cpy_node(int to, int from)
{
len[to] = len[from], fa[to] = fa[from];
memcpy(son[to], son[from], sizeof(son[to]));
}
void insert(int c)
{
int p = lst, np = lst = ++cur;
len[np] = len[p] + 1;
for ( ; p && (!son[p][c]); p = fa[p]) son[p][c] = np;
if (!p) fa[np] = 1;
else
{
int q = son[p][c];
if (len[q] == len[p] + 1) fa[np] = q;
else
{
int nq = ++cur;
cpy_node(nq, q), len[nq] = len[p] + 1;
fa[q] = fa[np] = nq;
for ( ; p && (son[p][c] == q); p = fa[p]) son[p][c] = nq;
}
}
}
void build()
{
for (int i = 1; i <= n; i++)
{
insert(s[i] - 'a');
pos[i] = lst;
}
}
}
namespace SGT
{
ll sum[sgt_sz], lazy[sgt_sz];
void push_up(int k) { sum[k] = sum[k << 1] + sum[k << 1 | 1]; }
void push_down(int k, int l, int r)
{
if (!lazy[k]) return;
int mid = (l + r) >> 1;
sum[k << 1] += 1ll * (mid - l + 1) * lazy[k];
sum[k << 1 | 1] += 1ll * (r - mid) * lazy[k];
lazy[k << 1] += lazy[k], lazy[k << 1 | 1] += lazy[k];
lazy[k] = 0ll;
}
void update(int k, int l, int r, int ql, int qr, int w)
{
if ((l >= ql) && (r <= qr))
{
sum[k] += 1ll * (r - l + 1) * w, lazy[k] += w;
return;
}
push_down(k, l, r);
int mid = (l + r) >> 1;
if (ql <= mid) update(k << 1, l, mid, ql, qr, w);
if (qr > mid) update(k << 1 | 1, mid + 1, r, ql, qr, w);
push_up(k);
}
ll query(int k, int l, int r, int ql, int qr)
{
if ((l >= ql) && (r <= qr)) return sum[k];
push_down(k, l, r);
int mid = (l + r) >> 1;
ll res = 0;
if (ql <= mid) res += query(k << 1, l, mid, ql, qr);
if (qr > mid) res += query(k << 1 | 1, mid + 1, r, ql, qr);
return res;
}
}
namespace LCT
{
#define ls son[x][0]
#define rs son[x][1]
int fa[sam_sz], son[sam_sz][2];
int top, q[sam_sz], val[sam_sz], lazy[sam_sz];
bool rev[sam_sz];
bool get(int x) { return (son[fa[x]][1] == x); }
bool is_root(int x) { return (son[fa[x]][0] != x) && (son[fa[x]][1] != x); }
void push_down(int x)
{
if (!lazy[x]) return;
if (ls) val[ls] = lazy[ls] = lazy[x];
if (rs) val[rs] = lazy[rs] = lazy[x];
lazy[x] = 0;
}
void rotate(int x)
{
int y = fa[x], z = fa[y], k = get(x);
son[y][k] = son[x][k ^ 1], fa[son[x][k ^ 1]] = y;
son[x][k ^ 1] = y;
if (!is_root(y)) son[z][son[z][1] == y] = x;
fa[x] = z, fa[y] = x;
}
void splay(int x)
{
q[top = 1] = x;
for (int i = x; !is_root(i); i = fa[i]) q[++top] = fa[i];
for (int i = top; i; i--) push_down(q[i]);
while (!is_root(x))
{
int y = fa[x], z = fa[y];
if (!is_root(y)) rotate(get(x) == get(y) ? y : x);
rotate(x);
}
}
void access(int x, int cp)
{
int nd = 0;
for (int t = 0; x; t = x, x = fa[x])
{
splay(x);
if (int p = val[x]) SGT::update(1, 1, n, p - SAM::len[x] + 1, p - SAM::len[fa[x]], -1);
rs = t, nd = x;
}
val[nd] = lazy[nd] = cp;
SGT::update(1, 1, n, 1, cp, 1);
}
void build() { for (int i = 2; i <= SAM::cur; i++) fa[i] = SAM::fa[i]; }
}
struct Query
{
int l, r, idx;
bool operator < (const Query& rhs) const { return (r < rhs.r); }
} qry[maxq];
int main()
{
scanf("%s%d", s + 1, &q);
n = strlen(s + 1);
SAM::build();
LCT::build();
for (int i = 1; i <= q; i++)
{
qry[i].idx = i;
scanf("%d%d", &qry[i].l, &qry[i].r);
}
sort(qry + 1, qry + q + 1);
for (int i = 1, cur = 1; i <= q; i++)
{
while (cur <= qry[i].r) LCT::access(pos[cur], cur), cur++;
ans[qry[i].idx] = SGT::query(1, 1, n, qry[i].l, qry[i].r);
}
for (int i = 1; i <= q; i++) printf("%lld\n", ans[i]);
return 0;
}