题目链接
题目解法
很好的一道题,让我对根号分治及
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 种暴力
- 把
S
k
S_k
Sk 的前缀(不是上面的前缀,而是字符串的前缀)在
f
a
i
l
fail
fail 树上标记出,然后做一个子树累加
那么每个字符串在 S k S_k Sk 中的出现次数即为 f a i l fail fail 树上末位置对应的子树和 - 从前往后遍历每个字符串,把
f
a
i
l
fail
fail树上这个字符串对应的子树
+
1
+1
+1
然后对于 S k S_k Sk,遍历 S k S_k Sk 的前缀,求 f a i l fail fail 树的单点值累加即可
考虑糅合两个做法:根号分治
对于
≥
B
\ge B
≥B 的字符串,用做法 1
对于
<
B
<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 <bits/stdc++.h>
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;
}