CF方向
Luogu方向
很好的一道题,让我对根号分治及 A C AC AC 自动机有了更深的理解
看到字符串出现次数的题,首先想到建立 A C AC AC 自动机
如何找到字符串 x x x 在字符串 y y y 中的出现次数?字符串 y y y 在 t r i e trie trie 树上一直往上跳,然后再 f a i l fail fail 树上看当前 y y y 的前缀对应点是否在 x x x 对应点的子树内,然后累加
考虑把 [ l , r ] [l,r] [l,r] 的字符串变为 [ 1 , r ] [1,r] [1,r] 的字符串 − [ 1 , l − 1 ] -\;[1,l-1] −[1,l−1] 的字符串
我们需要计算一个前缀中的字符串在 S k S_k Sk 中的出现次数
现在可以得到 2 种暴力
考虑糅合两个做法:根号分治
对于 ≥ B \ge B ≥B 的字符串,用做法 1
对于 < B <B 的字符串,用做法 2
两个做法都需要离线
理论上 B B B 取 n \sqrt n n 最优,时间复杂度为 O ( n n ) O(n\sqrt n) O(nn)(这里不分 n , q n,q n,q 和字符串的总长度)
但是空间有些开不下,所以可以调小块长
#include
using namespace std;
typedef long long LL;
const int N=100100,MAXB=295;
struct Node{ int k,neg,id;};
int n,q,lenth[N];
string str[N];
int tr[N][26],idx,ne[N],ed[N];
int bigidx,rv[N];
int L[N],R[N],dfnidx=-1;
int e[N],NE[N],h[N],IDX;
vector<Node> query[N];
int que[N],sum[N];
LL ans[N],pref[MAXB][N];
inline int read(){
int FF=0,RR=1;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
return FF*RR;
}
void add(int x,int y){ e[IDX]=y,NE[IDX]=h[x],h[x]=IDX++;}
int insert(string s){
int p=0;
for(int i=0;i<s.size();i++){
int c=s[i]-'a';
if(!tr[p][c]) tr[p][c]=++idx;
p=tr[p][c];
}
return p;
}
void build_ACAM(){
int hh=0,tt=-1;
for(int i=0;i<26;i++) if(tr[0][i]) que[++tt]=tr[0][i];
while(hh<=tt){
int u=que[hh++];
for(int i=0;i<26;i++){
int v=tr[u][i];
if(!v) tr[u][i]=tr[ne[u]][i];
else ne[v]=tr[ne[u]][i],que[++tt]=v;
}
}
}
void dfs(int u){
for(int i=h[u];~i;i=NE[i]) dfs(e[i]),sum[u]+=sum[e[i]];
}
void dfs_dfn(int u){
L[u]=++dfnidx;
for(int i=h[u];~i;i=NE[i]) dfs_dfn(e[i]);
R[u]=dfnidx;
}
struct BLOCK{
int LIM,pos[N],tag1[N],tag2[N];
void build(){
LIM=sqrt(idx);
for(int i=1;i<=idx;i++) pos[i]=(i-1)/LIM+1;
}
void add(int l,int r){
if(pos[l]==pos[r]) for(int i=l;i<=r;i++) tag1[i]++;
else{
int i=l,j=r;
while(pos[i]==pos[l]) tag1[i]++,i++;
while(pos[j]==pos[r]) tag1[j]++,j--;
for(int k=pos[i];k<=pos[j];k++) tag2[k]++;
}
}
int ask(int x){ return tag1[x]+tag2[pos[x]];}
}block;
int main(){
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>str[i],lenth[i]=str[i].size();
int m=0;
for(int i=1;i<=n;i++) m+=lenth[i];
for(int i=1;i<=n;i++) ed[i]=insert(str[i]);
build_ACAM();
memset(h,-1,sizeof(h));
for(int i=1;i<=idx;i++) add(ne[i],i);
int B=290;
for(int i=1;i<=n;i++)
if(lenth[i]>B){
rv[i]=++bigidx;
for(int j=0;j<=idx;j++) sum[j]=0;
int p=0;
for(int j=0;j<str[i].size();j++){
p=tr[p][str[i][j]-'a'];
sum[p]++;
}
dfs(0);
LL res=0;
for(int j=1;j<=n;j++) res+=sum[ed[j]],pref[bigidx][j]=res;
}
for(int i=1;i<=q;i++){
int l=read(),r=read(),k=read();
if(lenth[k]>B) k=rv[k],ans[i]=pref[k][r]-pref[k][l-1];
else query[r].push_back({k,1,i}),query[l-1].push_back({k,-1,i});
}
dfs_dfn(0);
block.build();
for(int i=1;i<=n;i++){
block.add(L[ed[i]],R[ed[i]]);
for(auto j:query[i]){
int x=j.k,p=0;
for(int k=0;k<str[x].size();k++){
p=tr[p][str[x][k]-'a'];
ans[j.id]+=j.neg*block.ask(L[p]);
}
}
}
for(int i=1;i<=q;i++) cout<<ans[i]<<'\n';
return 0;
}