LOJ2720 「NOI2018」你的名字 【SAM,线段树合并】

题目描述:给定串 \(S\)\(Q\) 次询问,每次询问串 \(T\) 和两个正整数 \(l,r\),求有多少个字符串是 \(T\) 的子串且不为 \(S[l:r]\) 的子串。

数据范围:\(|\Sigma|=26\)\(|S|,|T|\le 5\times 10^5\)\(\sum|T|\le 10^6\)。LOJ时限4s,洛谷时限5s。


首先看 \(l=1,r=|S|\) 如何做。先对 \(S\)\(T\) 构建 SAM,然后我们可以算出 \(l_i\) 表示 \(T[1,i]\) 的后缀并 \(S\) 的子串的长度最大值。这个可以用类似 AC 自动机的匹配方法在 SAM 上做匹配。\(i\) 右移的时候当前节点没有对应出边的话就缩小长度,否则走出边。根据 two-pointer,这个的时间复杂度是 \(O(|T|)\) 的。

那么答案就是 \(\sum_{i=2}^{cnt}\max(0,len[i]-\max(len[fa[i]],l[id[i]]))\),其中 \(cnt\) 为 SAM 的节点个数,\(len[i]\) 为该节点对应子串的最大长度,\(fa[i]\) 是 Parent 树上 \(i\) 的父亲,\(id[i]\) 是节点 \(i\) 对应的 right 集合中的一个数。因为在节点 \(i\) 表示的子串中,长度为 \((\max(len[fa[i]],l[id[i]]),len[i]]\) 的子串对答案有贡献。

\((l,r)\neq (1,|S|)\),则答案计算时是没有区别的(至与 \(T\)\(l[i]\) 有关),所以要改变计算 \(l[i]\) 的方法。注意此时有些出边是无效的了,因为该出边到达的节点表示的任意一个子串在 \([l,r]\) 没有出现过,这时我们就需要使用线段树合并来维护 right 集合,若该出边到达的节点在 \([l+Len,r]\) 上出现过(\(Len\) 为当前计算的最大长度),则可以转移该出边并将 \(Len\) 加一。

时间复杂度 \(O((|S|+\sum |T|)\log|S|)\),空间复杂度 \(O((|S|+|T|)|\Sigma|)\)。具体实现可以看代码~

#include<bits/stdc++.h>
#define Rint register int
using namespace std;
typedef long long LL;
const int N = 1000003, M = N << 1;
template<typename T>
inline void read(T &x){
	int ch = getchar(); x = 0;
	for(;ch < '0' || ch > '9';ch = getchar());
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
}
char S[N], T[N];
int n, m, L, R, Q, ls[N << 5], rs[N << 5], cnt;
void insert(int &x, int L, int R, int pos){
	if(!x) x = ++ cnt;
	if(L == R) return;
	int mid = L + R >> 1;
	if(pos <= mid) insert(ls[x], L, mid, pos);
	else insert(rs[x], mid + 1, R, pos);
}
int merge(int x, int y){
	if(!x || !y) return x ^ y;
	int o = ++ cnt;
	ls[o] = merge(ls[x], ls[y]);
	rs[o] = merge(rs[x], rs[y]);
	return o;
}
bool query(int x, int L, int R, int l, int r){
	if(!x) return 0;
	if(l <= L && R <= r) return 1;
	int mid = L + R >> 1;
	return l <= mid && query(ls[x], L, mid, l, r) || mid < r && query(rs[x], mid + 1, R, l, r);
}
namespace SAM1 {
	int ch[M][26], fa[M], len[M], rt[M], cnt = 1, las = 1, c[M], id[M];
	bool has[M];
	void insert(int c){
		int p = las, np = ++ cnt; len[np] = len[p] + 1; has[np] = true; las = np;
		for(;p && !ch[p][c];p = fa[p]) ch[p][c] = np;
		if(!p) fa[np] = 1;
		else {
			int q = ch[p][c];
			if(len[q] == len[p] + 1) fa[np] = q;
			else {
				int nq = ++ cnt; has[nq] = false; len[nq] = len[p] + 1;
				memcpy(ch[nq], ch[q], sizeof ch[q]);
				fa[nq] = fa[q]; fa[q] = fa[np] = nq; rt[nq] = rt[q];
				for(;ch[p][c] == q;p = fa[p]) ch[p][c] = nq;
			}
		}
	}
	void build(){
		for(Rint i = 1;i <= n;++ i) insert(S[i] - 'a');
		for(Rint i = 1;i <= cnt;++ i) ++ c[len[i]];
		for(Rint i = 1;i <= n;++ i) c[i] += c[i - 1];
		for(Rint i = cnt;i > 1;-- i) id[c[len[i]] --] = i;
		for(Rint i = cnt;i > 1;-- i){
			int p = id[i];
			if(has[p]) ::insert(rt[p], 1, n, len[p]);
			rt[fa[p]] = merge(rt[fa[p]], rt[p]);
		}
	}
}
namespace SAM2 {
	int ch[M][26], fa[M], len[M], id[M], l[M], cnt, las;
	void clear(){
		for(Rint i = 0;i <= cnt;++ i){
			memset(ch[i], 0, sizeof ch[i]);
			fa[i] = len[i] = id[i] = l[i] = 0;
		}
		cnt = las = 1;
	}
	void insert(int c){
		int p = las, np = ++ cnt; id[np] = len[np] = len[p] + 1; las = np;
		for(;p && !ch[p][c];p = fa[p]) ch[p][c] = np;
		if(!p) fa[np] = 1;
		else {
			int q = ch[p][c];
			if(len[q] == len[p] + 1) fa[np] = q;
			else {
				int nq = ++ cnt; len[nq] = len[p] + 1;
				memcpy(ch[nq], ch[q], sizeof ch[q]); id[nq] = id[q];
				fa[nq] = fa[q]; fa[q] = fa[np] = nq;
				for(;ch[p][c] == q;p = fa[p]) ch[p][c] = nq;
			}
		}
	}
	void main(){
		clear();
		scanf("%s", T + 1); m = strlen(T + 1);
		read(L); read(R);
		int Len = 0, now = 1;
		for(Rint i = 1;i <= m;++ i){
			int c = T[i] - 'a';
			insert(c);
			while(true){
				if(SAM1::ch[now][c] && query(SAM1::rt[SAM1::ch[now][c]], 1, n, L + Len, R)){
					now = SAM1::ch[now][c]; ++ Len; break;
				}
				if(!Len) break; -- Len;
				if(Len == SAM1::len[SAM1::fa[now]]) now = SAM1::fa[now];
			}
			l[i] = Len;
		}
		LL ans = 0;
		for(Rint i = 2;i <= cnt;++ i) ans += max(0, len[i] - max(len[fa[i]], l[id[i]]));
		printf("%lld\n", ans);
	}
}
int main(){
	scanf("%s", S + 1); n = strlen(S + 1); read(Q);
	SAM1 :: build();
	while(Q --) SAM2 :: main();
}
posted @ 2020-04-23 12:42  mizu164  阅读(187)  评论(0编辑  收藏  举报