题目描述
n
,
Q
≤
4
∗
1
0
5
n,Q\le4*10^5
n,Q≤4∗105
题目分析
建 S S S 的 SAM。记询问串为 T T T。
要数 S S S 中本质不同的串中 T T T 的出现次数之和
相当于给 T T T 后面接上一个串 R R R,前面接上一个串 L L L, L T R LTR LTR 出现在 S S S 中,求 L , R L,R L,R 的对数。
假设已经确定了 T R = p TR=p TR=p,考虑怎么数 L L L:以 p p p 为后缀的串,就是在 p p p 对应 fail 树上节点 q q q 子树中的串,加上 q q q 剩下长度对应的串。可以表示为 s u b t r e e [ q ] − ( ∣ p ∣ − l e n f a i l q − 1 ) subtree[q]-(|p|-len_{fail_q}-1) subtree[q]−(∣p∣−lenfailq−1),其中 s u b t r e e [ q ] subtree[q] subtree[q] 表示子树中的点的 l e n − l e n f a i l len-len_{fail} len−lenfail 之和。
然后考虑对所有的 R R R 统计上面的贡献。给 T T T 后面加字符,相当于在 SAM 的 DAG 上走,那么将 ∣ p ∣ |p| ∣p∣ 看做未知量,在 DAG 上做 DP,求出 i i i 点带入长度为 x x x 的串后往后走能够产生的贡献和,表示为 a x + b ax+b ax+b,那么每走一步, a x + b → a ( x + 1 ) + b = a x + a + b ax+b\rarr a(x+1)+b=ax+a+b ax+b→a(x+1)+b=ax+a+b
所以转移就是 a [ i ] = − 1 + ∑ a [ j ] , b [ i ] = s u b t r e e [ i ] + l e n f a i l i + 1 + ∑ a [ j ] + b [ j ] a[i]= -1+\sum a[j],~b[i]=subtree[i]+len_{fail_i}+1+\sum a[j]+b[j] a[i]=−1+∑a[j], b[i]=subtree[i]+lenfaili+1+∑a[j]+b[j]
每次询问时倍增找到 T T T 对应节点 t t t,输出 a [ t ] ∗ ∣ T ∣ + b [ t ] a[t]*|T|+b[t] a[t]∗∣T∣+b[t] 即可。
Code:
#include<bits/stdc++.h>
#define maxn 800005
#define LL long long
using namespace std;
char cb[1<<20],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<20,stdin),cs==ct)?0:*cs++)
void read(int &a){
char c;while(!isdigit(c=getc()));
for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
int n,Q,pos[maxn],f[maxn][20];
char s[maxn];
vector<int>G[maxn];
LL sub[maxn],a[maxn],b[maxn];
int fa[maxn]={-1},ch[maxn][26],len[maxn],last,sz;
void extend(int c){
int cur=++sz,p=last,q; len[last=cur]=len[p]+1;
for(;~p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
if(p==-1) fa[cur]=0;
else if(len[q=ch[p][c]]==len[p]+1) fa[cur]=q;
else{
int clone=++sz; len[clone]=len[p]+1,fa[clone]=fa[q];
memcpy(ch[clone],ch[q],sizeof ch[q]),fa[q]=fa[cur]=clone;
for(;~p&&ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
}
}
int main()
{
freopen("s2mple.in","r",stdin);
freopen("s2mple.out","w",stdout);
scanf("%d%d%s",&n,&Q,s+1);
for(int i=1;i<=n;i++) extend(s[i]-'a'),pos[i]=last;
for(int i=1;i<=sz;i++) G[len[i]].push_back(i);
//cerr<<sz<<endl;
for(int i=sz;i>=1;i--) for(int x:G[i]){
int f=fa[x];
sub[x]+=len[x]-len[f],sub[f]+=sub[x];
a[x]=-1,b[x]=sub[x]+len[f]+1;
for(int j=0,y;j<26;j++) if(y=ch[x][j]) a[x]+=a[y],b[x]+=a[y]+b[y];
}
for(int i=1;i<=sz;i++) f[i][0]=fa[i];
for(int j=1;j<=19;j++) for(int i=1;i<=sz;i++) f[i][j]=f[f[i][j-1]][j-1];
for(int l,r,x;Q--;){
read(l),read(r),x=pos[r];
for(int i=19;i>=0;i--) if(len[f[x][i]]>=r-l+1) x=f[x][i];
printf("%lld\n",a[x]*(r-l+1)+b[x]);
}
}
字符串集合只有 ab 的话,大样例节点数卡到了 799969,所以实现不好的话后缀自动机的复杂度还是要乘上字符集大小的。。