区间本质不同子串个数

复盘 \(\color{black}{{\rm P}}\color{red}{{\rm itiless0514}}\) 的 LCT ,找了一道好题做做。

Plus 版 这是 sol

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

posted @ 2022-03-13 15:57  Illusory_dimes  阅读(163)  评论(1编辑  收藏  举报