- 给定\(n\)个字符串,\(q\)次询问,每次求\(s_k\)在\(s_{l\sim r}\)中的出现次数总和。
- \(\sum|s|\le2\times10^5,q\le5\times10^5\)
\(AC\)自动机+树状数组
这种一堆字符串的题目,要么建\(AC\)自动机,要么建广义后缀自动机,其实这题两者都行。
最后我还是选择了\(AC\)自动机(主要太久没写过了)。
考虑把询问离线,变成\(s_{1\sim r}\)中\(s_k\)的出现次数减去\(s_{1\sim l-1}\)中\(s_k\)的出现次数。
先离线建出\(AC\)自动机,然后就变成一个不断往里面加串然后做全局询问的问题。
众所周知子串是前缀的后缀,所以我们加入一个串时对于每个前缀打上标记,对应的后缀就是\(fail\)树上的列祖列宗。
因此,询问的时候只要询问这个串在\(AC\)自动机上对应节点子树内的标记总和。
单点修改,子树求和,把\(fail\)树的\(dfs\)序列求出来用树状数组维护即可。
代码:\(O(qlogn)\)
#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 200000
#define M 500000
using namespace std;
int n,bg[N+5],l[N+5],ans[M+5];char s[N+5];
struct Q {int p,k,op;I Q(CI i=0,CI x=0,CI f=0):p(i),k(x),op(f){}};vector<Q> q[N+5];vector<Q>::iterator it;
namespace T//fail树
{
int d,dI[N+5],dO[N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N+5];
I void Add(CI x,CI y) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y;}//加边
I void Init(CI x) {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) {W(x<=T::d) ++a[x],x+=x&-x;}I int Q(RI x,RI t=0) {W(x) t+=a[x],x-=x&-x;return t;}//树状数组
}A;
namespace AC//AC自动机
{
int Nt=1,p[N+5],q[N+5];struct node {int F,S[30];}O[N+5];
I void Ins(CI id,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];p[id]=x;}
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) T::Add(O[i].F,i);T::Init(1);//建fail树预处理
}
I void U(char* s,CI l) {for(RI i=1,x=1;i<=l;++i) x=O[x].S[s[i]&31],A.U(T::dI[x]);}//给所有前缀打标记
I int Q(CI id) {return A.Q(T::dO[p[id]])-A.Q(T::dI[p[id]]-1);}//询问一个串对应节点子树内标记和
}
int main()
{
RI Qt,i,x,y,z;scanf("%d%d",&n,&Qt);
for(i=1;i<=n;++i) scanf("%s",s+(bg[i]=bg[i-1]+l[i-1])+1),AC::Ins(i,s+bg[i],l[i]=strlen(s+bg[i]+1));
for(AC::Build(),i=1;i<=Qt;++i) scanf("%d%d%d",&x,&y,&z),q[x-1].push_back(Q(i,z,-1)),q[y].push_back(Q(i,z,1));//把询问离线
for(i=1;i<=n;++i) for(AC::U(s+bg[i],l[i]),it=q[i].begin();it!=q[i].end();++it) ans[it->p]+=it->op*AC::Q(it->k);//不断加串做全局询问
for(i=1;i<=Qt;++i) printf("%d\n",ans[i]);return 0;
}