Part 1:l=1,r=|S|
先考虑部分数据:询问中满足
l
=
1
,
r
=
∣
S
∣
l=1,r=|S|
l=1,r=∣S∣
考虑
T
T
T串的一个前缀:对于
T
[
1...
i
]
T[1...i]
T[1...i],如果满足
T
[
p
.
.
.
i
]
(
1
≤
p
≤
i
)
T[p...i](1≤p≤i)
T[p...i](1≤p≤i)为
S
S
S的子串,且
T
[
p
−
1...
i
]
T[p-1...i]
T[p−1...i]不是
S
S
S的子串(我们令
T
[
p
−
1...
i
]
T[p-1...i]
T[p−1...i]为
T
[
1...
i
]
T[1...i]
T[1...i]对于
S
S
S的最长匹配后缀,令它的长度为
l
e
n
[
i
]
len[i]
len[i],显然
p
=
i
−
l
e
n
[
i
]
+
1
p=i-len[i]+1
p=i−len[i]+1。然而当一个也匹配不上时,
l
e
n
[
i
]
=
0
len[i]=0
len[i]=0),那么显然
T
[
1...
i
]
,
T
[
2...
i
]
,
T
[
3...
i
]
.
.
.
.
.
.
,
T
[
p
−
1...
i
]
T[1...i],T[2...i],T[3...i]......,T[p-1...i]
T[1...i],T[2...i],T[3...i]......,T[p−1...i]都是满足条件的(即是
T
T
T的一个非空连续子串且一定没有在
S
S
S中出现)
那么我们只要考虑对于每一个
i
i
i,求出它的
l
e
n
len
len值,
i
i
i位置的贡献就是
i
−
l
e
n
i-len
i−len。然而这样直接加起来是不对的。因为这里只统计本质不同的串。
为了解决这个问题,我们建立出
T
T
T串的后缀自动机。因为后缀自动机上所有节点包含的所有串即为
T
T
T串的所有本质不同的子串,可以达到去重的效果。而一个节点包含的所有串一定形如
c
d
e
,
b
c
d
e
,
a
b
c
d
e
cde,bcde,abcde
cde,bcde,abcde,长度连续且一个串是比它长的串的后缀,并且这些串的所有的出现位置是一样的。假设我们现在已经求出了
l
e
n
len
len数组。我们考虑一个节点的贡献。
我们假设这个节点里最短的串长为
m
i
n
L
minL
minL,最长的串长为
m
a
x
L
maxL
maxL,令它们在
T
T
T中出现的
e
n
d
p
o
s
endpos
endpos集合中的一个元素为
p
o
s
pos
pos(随便选一个位置即可,因为这些位置是等价的)。于是这个节点中的贡献(我们只考虑当前节点中的这些串是否在
S
S
S中出现!)就是
a
n
s
=
m
a
x
(
0
,
m
a
x
L
−
m
a
x
(
m
i
n
L
−
1
,
l
e
n
[
p
o
s
]
)
)
ans=max(0,maxL-max(minL-1,len[pos]))
ans=max(0,maxL−max(minL−1,len[pos]))
如下图为后缀自动机中的一个节点包含的串。绿色表示符合条件,红色表示不符。
(如果
l
e
n
[
p
o
s
]
>
=
m
a
x
L
len[pos]>=maxL
len[pos]>=maxL,那么该节点中所有点都不符合要求,因为它们肯定都在
S
S
S中出现过了,想想
l
e
n
len
len的含义。如果
l
e
n
[
p
]
<
m
i
n
L
len[p]<minL
len[p]<minL,说明它们肯定都没有在
S
S
S中出现过,那么这个节点中的所有串都符合要求)
那么我们最后在后缀自动机上跑一遍统计每个节点的答案就行了。
于是当前的问题就只剩下如何求出
l
e
n
len
len数组了——
考虑增量法:假设我们已经求出了
l
e
n
[
i
−
1
]
len[i-1]
len[i−1]。现在要往
T
T
T后面添加一个字符,并求出
l
e
n
[
i
]
len[i]
len[i]。它只用在
l
e
n
[
i
−
1
]
len[i-1]
len[i−1]的基础上修改就行了。
我们记
l
e
n
[
i
]
=
L
len[i]=L
len[i]=L。那么
L
L
L初始值为
l
e
n
[
i
−
1
]
len[i-1]
len[i−1]。在求解过程中,
L
L
L是在不断调整的。
第一种情况:就是
T
[
(
i
−
1
)
−
L
+
1...
i
]
T[(i-1)-L+1...i]
T[(i−1)−L+1...i]在
S
S
S中出现过,那么
L
=
L
+
1
L=L+1
L=L+1。因为
T
[
(
i
−
1
)
−
L
+
1...
i
]
T[(i-1)-L+1...i]
T[(i−1)−L+1...i]这个串不可能再往左边扩展了。(回去看
l
e
n
[
i
]
len[i]
len[i]定义)
第二种情况:我们发现
T
[
(
i
−
1
)
−
L
+
1...
i
]
T[(i-1)-L+1...i]
T[(i−1)−L+1...i]没有在
S
S
S中出现过,那么我们把最左边那个字符抠掉,并且把当前的
L
L
L减去
1
1
1。看现在这个串是否出现过。如果一直没有出现过直到
l
e
n
len
len值为
0
0
0,这个时候就可以退出,去求
l
e
n
[
i
+
1
]
len[i+1]
len[i+1]了。因为一个也匹配不上。
以上可以看做是
T
T
T串的两个指针在跳动。
这个过程可以通过跳
S
S
S的后缀自动机实现。后缀自动机上,从根节点到任意一个节点的路径上的字符连起来就代表着
S
S
S的一类子串(字符相同但位置不同)。当我们在上面跳,就相当于用 当前串 和
S
S
S的所有本质不同子串 去匹配。
上次的
T
[
(
i
−
1
)
−
L
+
1...
i
−
1
]
T[(i-1)-L+1...i-1]
T[(i−1)−L+1...i−1]已经和
S
S
S的某个子串匹配上了,我们令这个时候停在
u
u
u节点上,这就意味着从根节点到
u
u
u的某条路径可以构成
T
[
(
i
−
1
)
−
L
+
1...
(
i
−
1
)
]
T[(i-1)-L+1...(i-1)]
T[(i−1)−L+1...(i−1)]。然后我们有当前字符
T
[
i
]
=
c
T[i]=c
T[i]=c,现在要做的事情就是看
T
[
i
−
L
+
1...
i
]
T[i-L+1...i]
T[i−L+1...i]是否在
S
S
S中出现过。如果
u
u
u通过边
c
c
c连向
v
v
v,说明它出现了。我们可以直接跳到
v
v
v上,并且把
L
L
L加一,这时
T
[
i
−
L
+
1...
i
]
T[i-L+1...i]
T[i−L+1...i]是
S
S
S的子串。而如果不匹配,我们就把
L
L
L减去一。直到
T
[
i
−
L
+
1...
i
]
T[i-L+1...i]
T[i−L+1...i]成为
S
S
S的子串或
L
=
0
L=0
L=0。
在上述过程中,如果当前串
T
[
(
i
−
1
)
−
L
+
1...
i
]
T[(i-1)-L+1...i]
T[(i−1)−L+1...i]已经不在节点
u
u
u的包含范围内了,我们就把
u
u
u跳到它
p
a
r
e
n
t
parent
parent树的父节点上去。因为它的
e
n
d
p
o
s
endpos
endpos集合变大了。匹配的机会也变多了。
那么最后得出的 L L L就是 l e n [ i ] len[i] len[i]。综上, l = 1 , r = ∣ S ∣ l=1,r=|S| l=1,r=∣S∣的情况便得以解决。
Part 2:l,r任意
这时我们只需要多关心一个问题:
T
T
T的某个子串如果在
S
S
S中出现,它出现在哪里?
令该子串的长度为
l
e
n
g
t
h
length
length,那么如果它在
S
S
S中的
e
n
d
p
o
s
endpos
endpos集合至少有一个元素
p
o
s
pos
pos满足
p
o
s
∈
[
l
+
l
e
n
g
t
h
,
r
]
pos∈[l+length,r]
pos∈[l+length,r],就意味着它在询问的
S
[
l
,
r
]
S[l,r]
S[l,r]范围中出现过。
我们所要做修改的步骤是
T
T
T和
S
S
S的匹配过程,也就是求
l
e
n
len
len的过程。
这里唯一不同的一点便是——现在如果在
u
u
u节点上,有边
c
c
c连向
v
v
v,我们不能直接跳过去。
因为如果
T
[
i
−
L
+
1...
i
]
T[i-L+1...i]
T[i−L+1...i]的
e
n
d
p
o
s
endpos
endpos集合中没有任何元素在
[
l
+
L
,
r
]
[l+L,r]
[l+L,r]范围内,它也不和
S
S
S匹配。这便是我们要判断的。
这个时候我们用线段树维护
S
S
S的后缀自动机上节点的
e
n
d
p
o
s
endpos
endpos集合。
线段树中节点的
l
,
r
l,r
l,r表示的是在字符串中的位置。
当我们建立
S
S
S的后缀自动机,每插入一个节点时,我们在这个节点记下插入的位置。这相当于一个标记——从当前节点到根节点的
e
n
d
p
o
s
endpos
endpos集合中都包含这个位置。建完之后再在自动机的
p
a
r
e
n
t
parent
parent树上从下往上把线段树合并,即把父节点变为它跟儿子合并之后的节点。这样就维护了每个节点的
e
n
d
p
o
s
endpos
endpos集合。
这里的线段树用了动态开点。也就是说,有这个节点表示:这个节点所代表的范围内有 e n d p o s endpos endpos。询问的时候只用看这个节点是否存在即可。
代码中的 t a g tag tag只是随意找的一个位置。之前说过了。
#include<bits/stdc++.h>
#define ll long long
#define mid ((l+r)>>1)
using namespace std;
const int maxn=5e5+10;
char S[maxn],T[maxn<<1];int ss,tt;
int Q,l,r,L,R,cnt,tot=0,p[maxn<<1],lim[maxn<<1];
namespace SGT{
struct Node{int l,r;}t[maxn<<6];
int root[maxn<<1];
inline int merge(int x,int y){
if(!x||!y) return x+y;
int now=++tot;
t[now].l=merge(t[x].l,t[y].l);
t[now].r=merge(t[x].r,t[y].r);
return now;
}
inline void insert(int &rt,int l,int r,int pos){
if(!rt) rt=++tot;
if(l==r) return;
if(pos<=mid) insert(t[rt].l,l,mid,pos);
else insert(t[rt].r,mid+1,r,pos);
}
inline int query(int rt,int l,int r,int x,int y){
if(!rt||x>y) return 0;
if(x<=l&&r<=y) return 1;
if(x<=mid&&query(t[rt].l,l,mid,x,y)) return 1;
if(y>mid&&query(t[rt].r,mid+1,r,x,y)) return 1;
return 0;
}
}
using namespace SGT;
namespace SAM{
struct node{
int link,len,tag,nxt[26];
inline void clear(){tag=link=len=0,memset(nxt,0,sizeof(nxt));}
};
struct Sam1{
node st[maxn<<1];int last,sz;
int sum[maxn],p[maxn<<1];
inline void init(){
for(int i=0;i<=sz;++i) st[i].clear();
last=sz=0,st[0].link=-1;
}
inline void build(int c){
int cur=++sz,p=last;
st[cur].len=st[last].len+1;
for(;p!=-1&&!st[p].nxt[c];p=st[p].link)
st[p].nxt[c]=cur;
if(p==-1) st[cur].link=0;
else{
int q=st[p].nxt[c];
if(st[p].len+1==st[q].len) st[cur].link=q;
else{
int clone=++sz;st[clone]=st[q];
st[clone].len=st[p].len+1;
for(;p!=-1&&st[p].nxt[c]==q;p=st[p].link)
st[p].nxt[c]=clone;
st[cur].link=st[q].link=clone;
}
}last=cur;
}
inline void pre_work(){
for(int i=1;i<=sz;++i) sum[st[i].len]++;
for(int i=1;i<=ss;++i) sum[i]+=sum[i-1];
for(int i=1;i<=sz;++i) p[sum[st[i].len]--]=i;
for(int i=sz;i>=1;--i) root[st[p[i]].link]=merge(root[st[p[i]].link],root[p[i]]);
}
}Sam_S;
struct Sam2{
node st[maxn<<1];int last,sz;
inline void init(){
for(int i=0;i<=sz;++i) st[i].clear();
last=sz=0,st[0].link=-1;
}
inline void build(int c,int id){
int cur=++sz,p=last;
st[cur].len=st[p].len+1,st[cur].tag=id;
for(;p!=-1&&!st[p].nxt[c];p=st[p].link)
st[p].nxt[c]=cur;
if(p==-1) st[cur].link=0;
else{
int q=st[p].nxt[c];
if(st[p].len+1==st[q].len) st[cur].link=q;
else{
int clone=++sz;st[clone]=st[q];
st[clone].len=st[p].len+1;
for(;p!=-1&&st[p].nxt[c]==q;p=st[p].link)
st[p].nxt[c]=clone;
st[cur].link=st[q].link=clone;
}
}last=cur;
}
inline ll calc(ll ret=0){
for(int i=1;i<=sz;++i) ret+=max(0,st[i].len-max(st[st[i].link].len,lim[st[i].tag]));
return ret;
}
}Sam_T;
}
using namespace SAM;
int main(){
//freopen("3675.in","r",stdin);
scanf("%s%d",S+1,&Q),ss=strlen(S+1),Sam_S.init();
for(int i=1;i<=ss;++i)
Sam_S.build(S[i]-'a'),insert(root[Sam_S.last],1,ss,i);
Sam_S.pre_work();
while(Q--){
scanf("%s%d%d",T+1,&L,&R);
tt=strlen(T+1),Sam_T.init();
for(int i=1,u=0,len=0,c,v;i<=tt;++i){
c=T[i]-'a',Sam_T.build(c,i);
while(1){
v=Sam_S.st[u].nxt[c];
if(v&&query(root[v],1,ss,L+len,R)){u=v,++len;break;}
if(!len) break;
if((--len)==Sam_S.st[Sam_S.st[u].link].len) u=Sam_S.st[u].link;
}lim[i]=len;
}printf("%lld\n",Sam_T.calc());
}
}