区间本质不同子串个数
复盘 \(\color{black}{{\rm P}}\color{red}{{\rm itiless0514}}\) 的 LCT ,找了一道好题做做。
Description
给定长度为 \(n\) 的模式串, \(m\) 次询问,求区间本质不同子串个数。
\(n \leq 10 ^ 5,\ \ m \leq 2 \cdot 10 ^ 5\)
Analysis
没有修改,静态的,好,这样 SAM 就能派上用场了。
但是求区间内的话,我们发现好像也只能重构,顶多能把被包含的区间排除,没啥用。
可能直接 SAM 是有点毛病的,所以想考虑点其他东西。
比如说求区间不同颜色数,静态的,有这么一种解法:
-
首先离线。
-
类似一个扫描线的想法,定好右端点,从左往右扫,用一个线段树或者树状数组记录,每次加入一个颜色 \(col_r\) 后,这个点的权值定位 \(1\) ,取消这个颜色上一个出现的地方的权值。
-
这样的话有查询右边界是 \(r\) 就可以找区间权值和,因为这样相同颜色只会算一次,所以没有问题。
Solution
那其实这道题也能有类似的想法,但是每次新加进来一个字符可能会多很多种子串,咋办捏??
还是回归到 SAM 上,加入一个新字符之后会有一些字串对应的状态的 \(endpos\) 会多一个位置,并且这一定是在一条链上(还是顶到了根的那种)的状态,因为一些小性质(具体是什么不细讲了)。
然后发现越想越像树点染色,并且也能联想到 LCT 的 \(access\) 操作。
那如果我们用上 LCT ,在 \(access\) 的时候更新一下新出现的字符串就行了吧。
那那怎么更新呢??
既然我们是选择了数据结构算区间和,又封死了右边界,所以我们就要在区间和上体现出来初始位置,于是就把在 \(j\) 出现的字符串的贡献标记到 \(j - len + 1\) 上。
更新的话我们就要取消在上次出现位置标记的地方的贡献,然后转到新地方再标记,这样的话因为区间的特殊性不管怎么样查询都不会算漏。
因为这些如果要出现就全部是连续出现,所以可以区间修改。因为我们已经把 SAM 搬到 LCT 上面了,所以我们在 \(access\) 的时候对于一个状态取消上次的贡献,最后全局加就行了。
线段树和树状数组都可以捏。
Code
Code
/*
*/
#include
using namespace std;
typedef long long ll;
const int N = 4e5 + 10, M = 2e6 + 10;
int n, pos[N]; ll ans[N];
struct mdzz {
int l, r, id;
bool operator < (const mdzz &it) const {
return r < it.r;
}
} a[N];
inline int read() {
char ch = getchar();
int s = 0, w = 1;
while (!isdigit(ch)) {if (ch == '-') w = -1; ch = getchar();}
while (isdigit(ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar();}
return s * w;
}
struct SAM {
int n, cnt, las, len[N], link[N], ch[N][26];
char s[N]; int tong[N], rk[N];
inline void init() {cnt = las = 1; memset(ch[1], 0, sizeof(ch[1]));}
inline void SAM_stru(int c) {
int cur = ++cnt, p = las;
memset(ch[cur], 0, sizeof(ch[cur]));
las = cur;
len[cur] = len[p] + 1;
while (p && !ch[p][c]) ch[p][c] = cur, p = link[p];
if (!p) {link[cur] = 1; return ;}
int q = ch[p][c];
if (len[p] + 1 == len[q]) {link[cur] = q; return ;}
int clo = ++cnt;
link[clo] = link[q]; len[clo] = len[p] + 1;
link[q] = link[cur] = clo;
memcpy(ch[clo], ch[q], sizeof(ch[clo]));
while (p && ch[p][c] == q) ch[p][c] = clo, p = link[p];
}
inline void Tong_sort() {
for (int i = 1; i <= cnt; ++i) ++tong[len[i]];
for (int i = 1; i <= cnt; ++i) tong[i] += tong[i - 1];
for (int i = 1; i <= cnt; ++i) rk[tong[len[i]]--] = i;
}
} s;
struct SGT {
ll c[M], d[M];
inline int lowbit(int k) {return k & (-k);}
inline void add(int x, int y) {
for (int p = x; x <= s.n; x += lowbit(x)) c[x] += y, d[x] += p * y;
}
inline ll ask(int x) {
ll tmp = 0, res = 0, p = x;
for (; x; x -= lowbit(x)) tmp += c[x], res += d[x];
return tmp * (p + 1) - res;
}
inline void add(int l, int r, int k) {add(l, k); add(r + 1, -k);}
} seg;
struct LCT {
#define ls(x) ch[x][0]
#define rs(x) ch[x][1]
int ch[N][2], fa[N], xr[N], cov[N], val[N], len[N], top, sta[N];
inline void build() {
val[0] = 1e9;
for (int i = 1; i <= s.cnt; ++i) {
fa[i] = s.link[i];
val[i] = len[i] = s.len[fa[i]] + 1;
xr[i] = cov[i] = ls(i) = rs(i) = 0;
}
}
inline void pushcov(int x, int k) {xr[x] = cov[x] = k;}
inline void pushup(int x) {
val[x] = min(len[x], min(val[ls(x)], val[rs(x)]));
}
inline void pushdown(int x) {
if (cov[x]) {
if (ls(x)) pushcov(ls(x), cov[x]);
if (rs(x)) pushcov(rs(x), cov[x]);
cov[x] = 0;
}
}
inline bool nroot(int x) {return ls(fa[x]) == x || rs(fa[x]) == x;}
inline void rotate(int x) {
int y = fa[x], z = fa[y], k = rs(y) == x, w = ch[x][!k];
if (nroot(y)) ch[z][rs(z) == y] = x;
ch[x][!k] = y; ch[y][k] = w;
if (w) fa[w] = y;
fa[y] = x; fa[x] = z; pushup(y); pushup(x);
}
inline void splay(int x) {
int y = x, z; top = 0;
sta[++top] = y;
while (nroot(y)) sta[++top] = y = fa[y];
while (top) pushdown(sta[top--]);
while (nroot(x)) {
y = fa[x]; z = fa[y];
if (nroot(y)) rotate((ls(y) == x) ^ (ls(z) == y) ? x : y);
rotate(x);
}
}
inline void access(int x, int k) {
int y = 0;
while (x) {
splay(x);
if (xr[x]) seg.add(xr[x] - s.len[x] + 1, xr[x] - val[x] + 1, -1);
rs(x) = y; y = x, x = fa[x];
}
pushcov(y, k); seg.add(1, k, 1);
}
#undef ls
#undef rs
} lct;
int main() {
scanf("%s", s.s + 1); s.n = strlen(s.s + 1); s.init();
for (int i = 1; i <= s.n; ++i) {
s.SAM_stru(s.s[i] - 'a'); pos[i] = s.las;
}
s.Tong_sort();
n = read();
for (int i = 1; i <= n; ++i) {
a[i] = (mdzz) {read(), read(), i};
}
sort(a + 1, a + 1 + n);
lct.build();
for (int i = 1, j = 1; i <= n; ++i) {
while (j <= a[i].r) lct.access(pos[j], j), ++j;
ans[a[i].id] = seg.ask(a[i].r) - seg.ask(a[i].l - 1);
}
for (int i = 1; i <= n; ++i) printf("%lld\n", ans[i]);
return 0;
}