- 给定(n)个字符串,(q)次询问,每次求(s_{lsim r})在(s_k)中的出现次数总和。
- (n,q,sum|s|le10^5)
(AC)自动机+根号分治
由于询问的若干串之间不相干,显然可以把询问拆成用(s_{1sim r})的答案减去(s_{1,l-1})的答案,然后就变成了每次询问前(i)个串在(s_k)中的出现次数。
而众所周知,在(AC)自动机上,子串是前缀的后缀,前缀就是根到(s_k)路径上的每个节点,后缀就是一个节点的所有祖先。反过来也可以看作是根到(s_k)路径上的点在询问串对应的子树内。
考虑(|s_k|),把情况分成小于等于(sqrt N)和大于(sqrt N)两类。
如果(|s_k|lesqrt N),我们直接枚举把询问的(s_k)扔到(l-1)和(r)两个端点上,然后只要枚举每个串在(fail)树上给子树打标记(转化成(dfs)序列后用树状数组),然后处理对应端点上的询问求到(s_k)路径上的所有点的标记和即可。
如果(|s_k|>sqrt N),由于这样的串不超过(sqrt N)个,我们把询问扔给(s_k),对于每个这样的(s_k)分别求解答案。具体地,我们只要给到(s_k)路径上的所有点打上标记,枚举每个串求出子树内的标记和,然后前缀和+差分即可。
注意到这里的树状数组可以用分块替代消去(log),但实际上没啥意义,估计树状数组小常数跑得更快。
代码:(O(nsqrt nlogn))
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define LL long long
using namespace std;
int n,Nt=1,sz,id[N+5],bg[N+5],l[N+5];LL v[N+5],ans[N+5];char s[N+5];
struct Q {int p,x,y;I Q(CI i=0,CI a=0,CI b=0):p(i),x(a),y(b){}};vector<Q> V[N+5],G[N+5];vector<Q>::iterator it;
namespace AC//AC自动机
{
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
struct node {int F,S[30];}O[N+5];
int d,dI[N+5],dO[N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N+5];
I void Init(CI x=1) {dI[x]=++d;for(RI i=lnk[x];i;i=e[i].nxt) Init(e[i].to);dO[x]=d;}//处理dfs序
struct TreeArray
{
int a[N+5];I void U(RI x,CI v) {W(x<=Nt) a[x]+=v,x+=x&-x;}//单点修改/后缀修改
I int Q(RI x,RI t=0) {W(x) t+=a[x],x-=x&-x;return t;}//前缀询问/单点询问
}A;
I int Ins(char* s,CI l)//插入一个串
{
RI x=1;for(RI i=1,t;i<=l;++i) !O[x].S[t=s[i]&31]&&(O[x].S[t]=++Nt),x=O[x].S[t];return x;//返回节点编号
}
int q[N+5];I void Build()//建AC自动机
{
RI i,k,H=1,T=0;for(i=1;i<=26;++i) (O[1].S[i]?O[q[++T]=O[1].S[i]].F:O[1].S[i])=1;
W(H<=T) for(k=q[H++],i=1;i<=26;++i) (O[k].S[i]?O[q[++T]=O[k].S[i]].F:O[k].S[i])=O[O[k].F].S[i];
for(i=2;i<=Nt;++i) add(O[i].F,i);Init();//建fail树
}
I void P(char* s,CI l,CI v) {for(RI i=1,x=1;i<=l;++i) x=O[x].S[s[i]&31],A.U(dI[x],v);}//给路径上所有点打标记
I int G(CI x) {return A.Q(dO[x])-A.Q(dI[x]-1);}//询问子树标记和
I void U(CI x) {A.U(dI[x],1),A.U(dO[x]+1,-1);}//给子树打标记
I LL Q(char* s,CI l) {LL t=0;for(RI i=1,x=1;i<=l;++i) x=O[x].S[s[i]&31],t+=A.Q(dI[x]);return t;}//求路径上所有点标记和
}
int main()
{
RI Qt,i,j;for(scanf("%d%d",&n,&Qt),i=1;i<=n;++i)
bg[i]=bg[i-1]+l[i-1],scanf("%s",s+bg[i]+1),id[i]=AC::Ins(s+bg[i],l[i]=strlen(s+bg[i]+1));
RI x,y,k;for(sz=sqrt(n),i=1;i<=Qt;++i) scanf("%d%d%d",&x,&y,&k),
l[k]<=sz?(V[x-1].push_back(Q(i,k,-1)),V[y].push_back(Q(i,k,1))):G[k].push_back(Q(i,x,y));//根号分治
for(AC::Build(),i=1;i<=n;++i) if(!G[i].empty())//对于长度超过sqrt(n)的串
{
for(AC::P(s+bg[i],l[i],1),j=1;j<=n;++j) v[j]=v[j-1]+AC::G(id[j]);//前缀和
for(it=G[i].begin();it!=G[i].end();++it) ans[it->p]=v[it->y]-v[it->x-1];AC::P(s+bg[i],l[i],-1);//差分回应询问
}
for(i=1;i<=n;++i)//对于长度不超过sqrt(n)的串
{
for(AC::U(id[i]),it=V[i].begin();it!=V[i].end();++it) ans[it->p]+=it->y*AC::Q(s+bg[it->x],l[it->x]);//加入每个前缀,枚举端点上的询问
}
for(i=1;i<=Qt;++i) printf("%lld
",ans[i]);return 0;
}