区间本质不同子串个数(SAM+LCT+线段树)

题目:洛谷P6292

题目描述:

给你一个长度为\(n\)的字符串\(s\)\(m\)次询问,第\(i\)次询问\(s\)上的一个区间\([l_{i},r_{i}]\)上有多少个本质不同的子串

\(n \leq 10^{5}\)\(m \leq 2 \cdot 10^{5}\)

蒟蒻题解:

想到一个经典问题:求区间不同元素个数

一个做法是将这些区间\([l_{i},r_{i}]\)存在节点\(r_{i}\)中,对于一种元素\(x\),记它最后出现的位置\(last[x]\),这样等同于求\([l_{i},r_{i}]\)中有多少个位置作为某种元素最后出现的位置,对于新加入一个元素\(x\),把原本的\(last[x]\)标记清除,把当前位置打上个标记,这样可以用线段树或树状数组求某段区间有多少个位置被打上标记

\(s[i..j]\)表示字符串\(s\)从第\(i\)位到第\(j\)位这一段连续字符组成的字符串

要求区间本质不同子串个数,与所有子串有关,比较容易想到后缀数组或后缀自动机

考虑建后缀自动机,由于每次拓展出一个字符,后缀自动机中会加入至多两个节点,一个是代表的是当前\(s[1..i]\)\(endpos\)(即结尾只出现在当前位置\(i\)的子串),另一个是后缀与\(s[1..i]\)相同但是\(endpos\)不同的最长的子串,对于整个后缀自动机建完与后缀自动机中间过程,前面的点改变的后缀链接\(link\)并不是真的改变,只有这个点后缀链接指向的点拆点后才会改变,指向的还是相同的\(endpos\),所以整个后缀自动机建完再去处理中间的状态是可以的(刚开始没搞懂这个,一直想不清楚,对后缀自动机理解不够深)

将整个后缀自动机建完后,套用刚刚“求区间不同元素个数”的做法,对于每个位置,记录一个值,表示以当前位置为最后一次出现的位置的开头(字符串有长度,有\(l_{i}\)的限制,所以取开头)的字符串的个数

当右端点往右扩时,把以这个右端点结尾的所有字符串最后一次出现的位置的开头位置所记录的值\(-1\),然后将这些字符串这次出现的开头位置\(+1\),容易发现他是连续的,\(+1\)相当于是在\([1,i]\)这个区间的所有位置记录的值\(+1\)

但是找以这个右端点结尾的所有字符串的开头位置不好找

如果以每个结尾位置右端点为\(i\)的字符串涂上颜色\(i\),可以发现对于颜色\(i\),它在后缀自动机上从它所对应的节点一直涂到根节点,最终后缀自动机后缀链接组成的树上,每种颜色一定是出现在连续的一条链上

考虑\(lct\)维护染色联通块,对于每次找开头位置,暴力\(access\),边修改,这是均摊\(log\ n\)

至于维护每个位置所记录的值,区间\(+1\)/\(-1\),区间求和,直接用线段树维护就可以了

\(lct\)暴力\(access\)均摊\(log\ n\),线段树修改\(log\ n\),总的时间复杂度是\(\Theta(n\ log^{2}n)\)

参考程序:

#include<bits/stdc++.h>
using namespace std;
#define Re register int
typedef long long ll;

const int N = 200005;
struct info
{
	int id, t;
};
int n, m, lst, num, res, g[N], ls[N], len[N], lk[N], ch[N][28], lt[N], son[N][2];
char s[N];
bool lz[N];
ll sum[N << 2], ad[N << 2], ans[N];
vector<info> q[N];

inline int read()
{
	char c = getchar();
	int ans = 0;
	while (c < 48 || c > 57) c = getchar();
	while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
	return ans;
}

inline void write(ll x)
{
	int num = 0;
	char sc[25];
	if (!x) sc[num = 1] = 48;
	while (x) sc[++num] = x % 10 + 48, x /= 10;
	while (num) putchar(sc[num--]);
	putchar('\n');
}

inline void push_down(int id, int l, int mid, int r)
{
	sum[id << 1] += ad[id] * (mid - l + 1), sum[id << 1 | 1] += ad[id] * (r - mid);
	ad[id << 1] += ad[id], ad[id << 1 | 1] += ad[id];
	ad[id] = 0;
}

inline void modfy(int id, int l, int r, int x, int y, int z)
{
	if (x <= l && r <= y)
	{
		sum[id] += 1ll * z * (r - l + 1), ad[id] += z;
		return;
	}
	int mid = l + r >> 1;
	if (ad[id]) push_down(id, l, mid, r);
	if (x <= mid) modfy(id << 1, l, mid, x, y, z);
	if (y > mid) modfy(id << 1 | 1, mid + 1, r, x, y, z);
	sum[id] = sum[id << 1] + sum[id << 1 | 1];
}

inline ll que(int id, int l, int r, int x, int y)
{
	if (x <= l && r <= y) return sum[id];
	int mid = l + r >> 1;
	ll ans = 0;
	if (ad[id]) push_down(id, l, mid, r);
	if (x <= mid) ans = que(id << 1, l, mid, x, y);
	if (y > mid) ans += que(id << 1 | 1, mid + 1, r, x, y);
	return ans;
}

inline bool check(int x)
{
	return son[lk[x]][0] == x || son[lk[x]][1] == x;
}

inline void pushdown(int x)
{
	if (son[x][0]) lt[son[x][0]] = lt[x], lz[son[x][0]] = 1;
	if (son[x][1]) lt[son[x][1]] = lt[x], lz[son[x][1]] = 1;
	lz[x] = 0;
}

inline void rotate(int x)
{
	int y = lk[x], z = lk[y];
	bool t = son[y][1] ^ x;
	if (check(y)) son[z][son[z][1] == y] = x;
	lk[x] = z, lk[y] = x;
	if (son[x][t]) lk[son[x][t]] = y;
	son[y][t ^ 1] = son[x][t], son[x][t] = y;
}

inline void splay(int x)
{
	g[res = 1] = x;
	while (check(g[res])) g[res + 1] = lk[g[res]], ++res;
	while (res)
	{
		if (lz[g[res]]) pushdown(g[res]);
		--res;
	}
	while (check(x))
	{
		int y = lk[x];
		if (check(y)) rotate(((son[y][1] == x) ^ (son[lk[y]][1] == y)) ? x : y);
		rotate(x);
	}
}

inline int find(int x)
{
	if (!x) return 0;
	splay(x);
	while (son[x][1]) x = son[x][1];
	return len[x];
}

inline void access(int x, int y)
{
	for (Re i = 0; x; x = lk[i = x])
	{
		splay(x);
		if (lz[x]) pushdown(x);
		if (lt[x]) modfy(1, 1, n, lt[x] - len[x] + 1, lt[x] - len[lk[x]], -1);
		son[x][1] = i;
	}
	splay(1), lt[1] = y, lz[1] = 1;
}

inline void ins(int x)
{
	int y = ++num;
	len[y] = len[lst] + 1;
	while (lst && !ch[lst][x]) ch[lst][x] = y, lst = lk[lst];
	if (lst)
	{
		int q = ch[lst][x];
		if (len[q] == len[lst] + 1) lk[y] = q;
		else
		{
			len[++num] = len[lst] + 1, lk[num] = lk[q], lk[q] = lk[y] = num;
			memcpy(ch[num], ch[q], sizeof ch[num]);
			while (lst && ch[lst][x] == q) ch[lst][x] = num, lst = lk[lst];
		}
	}
	else lk[y] = 1;
	lst = y;
}

int main()
{
	scanf("%s", s + 1);
	n = strlen(s + 1), m = read();
	for (Re i = 0; i < m; ++i)
	{
		int u = read(), v = read();
		q[v].push_back(info{i, u});
	}
	lst = num = 1;
	for (Re i = 1; i <= n; ++i) ins(s[i] - 97), ls[i] = lst;
	for (Re i = 1; i <= n; ++i)
	{
		access(ls[i], i), modfy(1, 1, n, 1, i, 1);
		int u = q[i].size();
		for (Re j = 0; j < u; ++j) ans[q[i][j].id] = que(1, 1, n, q[i][j].t, i);
	}
	for (Re i = 0; i < m; ++i) write(ans[i]);
	return 0;
}
posted @ 2021-03-27 00:10  clfzs  阅读(296)  评论(0)    收藏  举报