题目描述:给定串 (S),(Q) 次询问,每次询问串 (T) 和两个正整数 (l,r),求有多少个字符串是 (T) 的子串且不为 (S[l:r]) 的子串。
数据范围:(|Sigma|=26),(|S|,|T|le 5 imes 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) eq (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
", ans);
}
}
int main(){
scanf("%s", S + 1); n = strlen(S + 1); read(Q);
SAM1 :: build();
while(Q --) SAM2 :: main();
}