题意:给一个串 SSS,qqq 次询问,每次给定串 TTT 和 l,rl,rl,r ,求有多少个本质不同的串是 TTT 的子串而不是 Sl…rS_{l\dots r}Sl…r 的子串。
∣S∣≤5×105,q≤105,∑∣T∣≤106|S|\leq 5\times 10^5,q\leq 10^5,\sum|T|\leq 10^6∣S∣≤5×105,q≤105,∑∣T∣≤106
先考虑所有询问 l=1,r=∣S∣l=1,r=|S|l=1,r=∣S∣ 怎么做。
先对 SSS 建个 SAM 出来,我们需要把每次询问的 TTT 放上去搞搞。如果用广义后缀自动机的话复杂度是和 SSS 有关的,做不了。所以要把 TTT 就看成一个串。
考虑把 TTT 放上去匹配,从 SAM 上的当前点不断跳 fail 直到有对应的转移边。这样可以得到 TTT 的每一个前缀 [1,i][1,i][1,i] 最长的 在 SSS 中出现过的 后缀长度,记为 lenilen_ileni。
但我们要求的是本质不同的串,还需要把上面那个东西去重。
我们一次加入 TTT 的每个字符,那么当前的一个后缀合法当且仅当其长度大于 lenilen_ileni。我们可以对 TTT 再单独建一个 SAM,维护每个结点 endpos 集合中的最大值,然后遍历每个结点统计答案即可。
然后对于 l,rl,rl,r 任意的情况,就是用可持久化线段树合并来显式维护 endpos 集合。
然后就是一通乱搞。
冷静分析一下,我们要做的是当前维护的串是否在 Sl…rS_{l\dots r}Sl…r 中出现过,也就是 [l+s−1,r][l+s-1,r][l+s−1,r] 中有没有这个点的 endpos 集合中的位置,其中 sss 为当前串长度。直接暴力丢掉第一个字符,如果当前结点丢完了再跳父亲。转移的时候额外判一下要去的结点对应区间有没有这个点的 endpos。
因为这个串可能被砍了一半,所以在更新 lenilen_ileni 的时候要在 [l,r][l,r][l,r] 里查一个 endpos 的最大值 mxmxmx,和 mx−l+1mx-l+1mx−l+1 取 min\minmin。
复杂度 O(nlogn)\Omicron(n\log n)O(nlogn)
#include <iostream>
#include <cstring>
#include <cctype>
#include <cstdio>
#define MAXN 2000005
using namespace std;
typedef long long ll;
int ch[MAXN<<4][2],sum[MAXN<<4],mxpos[MAXN<<4],cnt;
void modify(int& x,int l,int r,int k)
{if (!x) x=++cnt;++sum[x];if (l==r) return (void)(mxpos[x]=l);int mid=(l+r)>>1;if (k<=mid) modify(ch[x][0],l,mid,k);else modify(ch[x][1],mid+1,r,k);mxpos[x]=max(mxpos[ch[x][0]],mxpos[ch[x][1]]);
}
int merge(int x,int y)
{if (!x||!y) return x|y;int p=++cnt;ch[p][0]=ch[x][0],ch[p][1]=ch[x][1];ch[p][0]=merge(ch[p][0],ch[y][0]);ch[p][1]=merge(ch[p][1],ch[y][1]);sum[p]=sum[ch[p][0]]+sum[ch[p][1]];mxpos[p]=max(mxpos[ch[p][0]],mxpos[ch[p][1]]);return p;
}
int query(int x,int l,int r,int ql,int qr)
{if (!x) return 0;if (ql<=l&&r<=qr) return sum[x];if (qr<l||r<ql) return 0;int mid=(l+r)>>1;return query(ch[x][0],l,mid,ql,qr)+query(ch[x][1],mid+1,r,ql,qr);
}
int querymax(int x,int l,int r,int ql,int qr)
{if (!x) return 0;if (ql<=l&&r<=qr) return mxpos[x];if (qr<l||r<ql) return 0;int mid=(l+r)>>1;return max(querymax(ch[x][0],l,mid,ql,qr),querymax(ch[x][1],mid+1,r,ql,qr));
}
int a[MAXN],c[MAXN],pos[MAXN],n,m;
char s[MAXN],t[MAXN];
struct SAM
{int ch[MAXN][26],fa[MAXN],len[MAXN],rt[MAXN],mx[MAXN],las,tot;inline void insert(int c,int k,int type){int cur=++tot;int p=las;len[cur]=len[p]+1;for (;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;if (!p) fa[cur]=1;else{int q=ch[p][c];if (len[q]==len[p]+1) fa[cur]=q;else{int _q=++tot;len[_q]=len[p]+1;fa[_q]=fa[q],fa[q]=fa[cur]=_q;memcpy(ch[_q],ch[q],sizeof(ch[_q]));for (;ch[p][c]==q;p=fa[p]) ch[p][c]=_q;}}las=cur;if (type) modify(rt[cur],1,n,k);else mx[cur]=k;}inline void build(int type){for (int i=1;i<=tot;i++) c[i]=0;for (int i=1;i<=tot;i++) ++c[len[i]];for (int i=1;i<=tot;i++) c[i]+=c[i-1];for (int i=1;i<=tot;i++) a[c[len[i]]--]=i;for (int i=tot;i>=1;i--) if (fa[a[i]]) {if (type) rt[fa[a[i]]]=merge(rt[fa[a[i]]],rt[a[i]]);else mx[fa[a[i]]]=max(mx[fa[a[i]]],mx[a[i]]); }}inline void query(int l,int r){int cur=1,s=0,p=0;for (int i=1;i<=m;i++){while (cur>1&&(!ch[cur][t[i]-'a']||l+s-1>r||!::query(rt[ch[cur][t[i]-'a']],1,n,l+max(s,1)-1,r)))((--s)==len[fa[cur]])&&(cur=fa[cur]);(::query(rt[ch[cur][t[i]-'a']],1,n,l+max(s,1)-1,r))&&(cur=ch[cur][t[i]-'a'],++s,++p);pos[i]=max(0,min(s,querymax(rt[cur],1,n,l,r)-l+1));}}inline ll calc(){ll ans=0;for (int i=1;i<=tot;i++) ans+=max(0,len[i]-max(len[fa[i]],pos[mx[i]]));return ans;}inline void clear(){for (int i=1;i<=tot;i++) mx[i]=fa[i]=0,memset(ch[i],0,sizeof(ch[i]));las=tot=1;}inline SAM():las(1),tot(1){}
}S,T;
int main()
{scanf("%s",s+1);n=strlen(s+1);for (int i=1;i<=n;i++) S.insert(s[i]-'a',i,1);S.build(1);int q;scanf("%d",&q);while (q--){scanf("%s",t+1);m=strlen(t+1);for (int i=1;i<=m;i++) T.insert(t[i]-'a',i,0);T.build(0);int l,r;scanf("%d%d",&l,&r);S.query(l,r);printf("%lld\n",T.calc());T.clear();}return 0;
}