【题目】
CF
给定
n
n
n个串
a
i
a_i
ai,
Q
Q
Q组询问
a
l
∼
a
r
a_l\sim a_r
al∼ar中
a
x
a_{x}
ax出现了多少次。
∑
∣
a
i
∣
≤
2
×
1
0
5
,
Q
≤
5
×
1
0
5
\sum |a_i| \leq 2\times 10^5,Q\leq 5\times 10^5
∑∣ai∣≤2×105,Q≤5×105
【解题思路】
这种东西显然
SAM
\text{SAM}
SAM线段树合并就可以做了吧。建出
SAM
\text{SAM}
SAM,那么询问就是子树中编号为某段区间
e
n
d
p
o
s
endpos
endpos的数量。我们以所在串编号建立线段树,线段树合并
r
i
g
h
t
right
right集合即可。
然而我又忘记SAM是节点数是两倍了
什么?不会 SAM \text{SAM} SAM?没有关系。
其实做这题的初衷就是练
AC
\text{AC}
AC自动机的,原理一样,但显然不够优秀。
我们建出 AC \text{AC} AC自动机以后,再用每个串跑一次,对于当前串 s s s前缀 s i s_i si,在其对应 f a i l fail fail树的位置贡献 + 1 +1 +1,那么这个前缀的所有后缀都会被贡献一次,即它 f a i l fail fail树子树中所有节点对于 s s s这个串的计数都会 + 1 +1 +1。
现在问题就是统计子树内代表串编号在
[
l
,
r
]
[l,r]
[l,r]的所有节点权值和。
观察到询问编号在
[
l
,
r
]
[l,r]
[l,r]这个东西是可以差分的,那么我们线段树的下标显然就不是编号了。一种显然的方式是,我们对
f
a
i
l
fail
fail树求出它的
d
f
s
dfs
dfs序,然后以此为下标建立线段树,可持久化一下就行了。
我们也可以使用离线 B I T BIT BIT的方式解决这个问题,可以获得更优秀的空间复杂度。
两种做法时间复杂度是一样的,都是 O ( ( n + q ) log n ) O((n+q)\log n) O((n+q)logn)。
两种做法都写了一次,明显感觉到了 SAM \text{SAM} SAM在处理某些问题时的优越性以及直观(在入门了以后)。
【参考代码】
SAM
+
\text{SAM}+
SAM+线段树合并
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+10,M=N*40;
int n,Q;
char s[N];
namespace IO
{
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
void write(int x){if(x>9)write(x/10);putchar(x%10^48);}
void writeln(int x){write(x);putchar('\n');}
}
using namespace IO;
namespace Tree
{
int rt[N];
struct Segment
{
int sz,ls[M],rs[M],sum[M];
void update(int &x,int l,int r,int p)
{
if(!x) x=++sz; ++sum[x];
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid) update(ls[x],l,mid,p);
else update(rs[x],mid+1,r,p);
}
int query(int x,int l,int r,int L,int R)
{
if(!x) return 0;
if(L<=l && r<=R) return sum[x];
int mid=(l+r)>>1,res=0;
if(L<=mid) res+=query(ls[x],l,mid,L,R);
if(R>mid) res+=query(rs[x],mid+1,r,L,R);
return res;
}
int merge(int x,int y)
{
if(!x || !y) return x+y;
int z=++sz;
ls[z]=merge(ls[x],ls[y]);
rs[z]=merge(rs[x],rs[y]);
sum[z]=sum[x]+sum[y];
return z;
}
}tr;
}
using namespace Tree;
namespace SAM
{
int p[N],b[N],c[N],id[N];
struct SAM
{
int sz,las,fa[N],mx[N],ch[N][26];
void extend(int x)
{
int p,q,np,nq;
if(ch[las][x])
{
p=las;q=ch[p][x];
if(mx[q]==mx[p]+1) {las=q;return;}
nq=++sz;mx[nq]=mx[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];fa[q]=nq;
for(;p && ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
las=nq;return;
}
p=las;np=las=++sz;mx[np]=mx[p]+1;
for(;p && !ch[p][x];p=fa[p]) ch[p][x]=np;
if(!p) fa[np]=1;
else
{
q=ch[p][x];
if(mx[q]==mx[p]+1) fa[np]=q;
else
{
nq=++sz;mx[nq]=mx[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];fa[np]=fa[q]=nq;
for(;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
}
}
}
void merge()
{
for(int i=1;i<=sz;++i) b[mx[i]]++;
for(int i=1;i<=sz;++i) b[i]+=b[i-1];
for(int i=sz;i;--i) c[b[mx[i]]--]=i;
for(int i=sz,x;i>1;--i) x=c[i],rt[fa[x]]=tr.merge(rt[x],rt[fa[x]]);
}
}S;
}
using namespace SAM;
int main()
{
#ifndef ONLINE_JUDGE
freopen("CF547E.in","r",stdin);
freopen("CF547E.out","w",stdout);
#endif
n=read();Q=read();S.sz=S.las=1;
for(int i=1,l;i<=n;++i)
{
scanf("%s",s+1);l=strlen(s+1);S.las=1;
for(int j=1;j<=l;++j) S.extend(s[j]-'a'),tr.update(rt[S.las],1,n,i);
p[i]=S.las;
}
S.merge();
while(Q--)
{
int l=read(),r=read(),id=read();
writeln(tr.query(rt[p[id]],1,n,l,r));
}
return 0;
}
AC \text{AC} AC自动机+主席树
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=2e5+10,M=N*40;
int n,Q;
namespace IO
{
int read()
{
int ret=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return ret;
}
void write(int x){if(x>9)write(x/10);putchar(x%10^48);}
void writeln(int x){write(x);putchar('\n');}
}
using namespace IO;
namespace Tree
{
vector<int>e[N];
int ind,rt[N],st[N],en[N];
struct Segment
{
int sz,ls[M],rs[M],sum[M];
void copy(int x,int y){ls[x]=ls[y];rs[x]=rs[y];sum[x]=sum[y];}
void pushup(int x){sum[x]=sum[ls[x]]+sum[rs[x]];}
void update(int y,int &x,int l,int r,int p)
{
//printf("%d %d %d %d %d\n",y,x,l,r,p);
x=++sz;copy(x,y);
if(l==r){sum[x]++;return;}
int mid=(l+r)>>1;
if(p<=mid) update(ls[y],ls[x],l,mid,p);
else update(rs[y],rs[x],mid+1,r,p);
pushup(x);
//printf("%d %d %d\n",l,r,sum[x]);
}
int query(int y,int x,int l,int r,int L,int R)
{
if(L<=l && r<=R) return sum[x]-sum[y];
int mid=(l+r)>>1,res=0;
if(L<=mid) res+=query(ls[y],ls[x],l,mid,L,R);
if(R>mid) res+=query(rs[y],rs[x],mid+1,r,L,R);
return res;
}
}tr;
void dfs(int x)
{
st[x]=++ind;
for(int i=0;i<(int)e[x].size();++i) dfs(e[x][i]);
en[x]=ind;
}
}
using namespace Tree;
namespace ACM
{
int ed[N],len[N];
char s[N];
vector<int>ts[N];
queue<int>q;
struct ACM
{
int sz,rt,fail[N],ch[N][26];
void init(){rt=sz=1;}
void in(int id)
{
scanf("%s",s+1);len[id]=strlen(s+1);int now=rt;
//printf("%d %d\n",id,len[id]);
for(int i=1;i<=len[id];++i)
{
ts[id].pb(s[i]-'a');
int x=s[i]-'a';
if(!ch[now][x]) ch[now][x]=++sz;
now=ch[now][x];
}
ed[id]=now;
}
void getfail()
{
q.push(rt);
while(!q.empty())
{
int x=q.front();q.pop();
for(int i=0;i<26;++i)
{
if(!ch[x][i]) continue;
int t=fail[x],t1=ch[x][i];
while(t && !ch[t][i]) t=fail[t];
fail[t1]=t?ch[t][i]:rt;
q.push(t1);
}
}
for(int i=1;i<=sz;++i) e[fail[i]].pb(i);
}
}S;
}
using namespace ACM;
void build(int y,int &x,int id)
{
int now=S.rt,las=y;
//printf("%d %d %d %d\n",id,len[id],st[ed[id]],en[ed[id]]);
for(int i=0;i<len[id];++i)
{
now=S.ch[now][ts[id][i]];
tr.update(las,x,1,ind,st[now]);las=x;
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("CF547E.in","r",stdin);
freopen("CF547E.out","w",stdout);
#endif
n=read();Q=read();S.init();
for(int i=1;i<=n;++i) S.in(i);
S.getfail();dfs(S.rt);
for(int i=1;i<=n;++i) build(rt[i-1],rt[i],i);
while(Q--)
{
int l=read(),r=read(),id=read();
printf("%d\n",tr.query(rt[l-1],rt[r],1,ind,st[ed[id]],en[ed[id]]));
}
return 0;
}