传送门

我有种自己根本没学过SAM的感觉……最后还是抄了老半天的题解……

首先,对$S$和每一次的$T$都建一个SAM

先考虑一下$l=1,r=\left| S \right|$的情况

设$lim_i$表示字符串$T[1..i]$能在$S$中匹配到的最长后缀(即$T[i-lim_i+1,i]$是$S$的子串且$lim_i$最大)(有可能不存在这个字符那么$lim_i=0$)

这个$lim_i$可以不断地在$S$的后缀自动机上跳来求出。当无法向下匹配时,一直跳parent树直到可以匹配为止

我们假设对于$T$的后缀自动机,每一个节点的$endpos$集合中所能代表的最长的字符串长度为$l_i$,$tag_i$表示该集合字符串第一次出现的结尾位置(因为集合里字符串互为后缀所以结尾相同),$fa_i$表示parent树上的父亲,$cnt$表示自动机节点总个数

那么答案就是$$ans=\sum_{i=2}^{cnt}max(0,l_i-max(l_{fa_i},lim_{tag_i}))$$

ps:这里的lim指的并不是上文的lim而是最长后缀的长度

上面式子的意思是,对于每一个节点,它不属于$S$的子串的总个数为当前节点代表的集合字符串个数减去与$S$有匹配的子串个数

然后只要在$T$的后缀自动机上枚举每一个节点就可以了

现在来考虑$l$和$r$任意的情况该怎么做

这个时候就要用线段树维护后缀自动机的$endpos$集合了(不明白这个怎么做的我简单说一下,就是搞一个动态开点线段树,如果一个节点的$endpos$集合里有某一个位置就把它加入以该点为根的树中,parent树上父亲节点的$endpos$集合必然包含儿子的$endpos$集合所以将每个点的$endpos$集合与它儿子的合并。然后查询这个节点是否有$endpos$位于某个区间中只要在线段树上查询看看这个区间代表的节点是否被开出来过就好了(因为线段树上只有存在的位置的节点被开出来过))

我们在处理$lim_i$集合的时候要注意,要判断当前节点是否在$S[l..r]$区间中出现过。设$p$为当前在$S$的自动机上跑到的节点,$len$表示匹配了$[i-len+1..i]$,因为要看能否转移到下一个节点,所以要匹配$[i-len,i]$,且$[l+len,r]$中有后继节点的$endpos$存在才行(可能说的烦了点,仔细想想为什么)

差不多就这样

 //minamoto
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
char sr[<<],z[];int C=-,Z;
inline void Ot(){fwrite(sr,,C+,stdout),C=-;}
inline void print(ll x){
if(C><<)Ot();
while(z[++Z]=x%+,x/=);
while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=1e6+,M=2e7+,inf=0x3f3f3f3f;
int q,n,m,rt[N],lim[N];char S[N];ll ans;
namespace tree{
int cnt,L[M],R[M];
void ins(int &p,int l,int r,int x){
if(!p) p=++cnt;
if(l==r) return;
int mid=(l+r)>>;
if(x<=mid) ins(L[p],l,mid,x);
else ins(R[p],mid+,r,x);
}
int merge(int x,int y){
if(!x||!y) return x+y;
int p=++cnt;
L[p]=merge(L[x],L[y]);
R[p]=merge(R[x],R[y]);
return p;
}
bool query(int p,int l,int r,int ql,int qr){
if(!p||ql>qr) return false;
if(ql<=l&&qr>=r) return true;
int mid=(l+r)>>;
if(ql<=mid&&query(L[p],l,mid,ql,qr)) return true;
if(qr>mid&&query(R[p],mid+,r,ql,qr)) return true;
return false;
}
}
namespace SAM{
int cnt=,last=;
int ch[N][],l[N],fa[N],c[N],a[N],in[N];
void ins(int c){
int p=last,np=++cnt;last=np,l[np]=l[p]+,in[np]=;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
if(!p) fa[np]=;
else{
int q=ch[p][c];
if(l[q]==l[p]+) fa[np]=q;
else{
int nq=++cnt;l[nq]=l[p]+;
memcpy(ch[nq],ch[q],sizeof(int)*());
fa[nq]=fa[q],fa[q]=fa[np]=nq;
for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
inline void calc(){
for(int i=;i<=n;++i) ins(S[i]-'a');
for(int i=;i<=cnt;++i) ++c[l[i]];
for(int i=;i<=cnt;++i) c[i]+=c[i-];
for(int i=;i<=cnt;++i) a[c[l[i]]--]=i;
for(int i=cnt,p;i;--i){
p=a[i];
if(in[p]) tree::ins(rt[p],,n,l[p]);
rt[fa[p]]=tree::merge(rt[fa[p]],rt[p]);
}
}
}
namespace solve{
int cnt=,last=;
int ch[N][],l[N],fa[N],c[N],a[N],tag[N];
inline void init(){
cnt=last=,memset(ch[],,sizeof(int)*());
}
inline int newnode(){
++cnt;memset(ch[cnt],,sizeof(int)*());return cnt;
}
void ins(int c){
int p=last,np=newnode();last=np,tag[np]=l[np]=l[p]+;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
if(!p) fa[np]=;
else{
int q=ch[p][c];
if(l[q]==l[p]+) fa[np]=q;
else{
int nq=newnode();l[nq]=l[p]+,tag[nq]=tag[q];
memcpy(ch[nq],ch[q],sizeof(int)*());
fa[nq]=fa[q],fa[q]=fa[np]=nq;
for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
ll solve(){
int L,R;
scanf("%s%d%d",S+,&L,&R);
init();
m=strlen(S+);
for(int len=,p=,i=;i<=m;++i){
int c=S[i]-'a';
ins(c);
while(true){
if(SAM::ch[p][c]&&tree::query(rt[SAM::ch[p][c]],,n,L+len,R)){
++len,p=SAM::ch[p][c];break;
}
if(len==) break;
--len;
if(len==SAM::l[SAM::fa[p]]) p=SAM::fa[p];
}
lim[i]=len;
}
ans=;
for(int i=;i<=cnt;++i)
ans+=max(,l[i]-max(l[fa[i]],lim[tag[i]]));
return ans;
}
}
int main(){
// freopen("testdata.in","r",stdin);
scanf("%s",S+);
n=strlen(S+);
SAM::calc();
scanf("%d",&q);
while(q--) print(solve::solve());
Ot();
return ;
}
05-20 16:28