分析:
我一开始的想法比较简单,建出
SAM
,每个结点记录有多少串使用了这个结点,直接匹配即可
结果竟然T了,按理来说也应该是WA啊
在看学姐的blog的时候,发现实际上就是这么搞:
我们直接用朴素的方法构建好
SAM
我们对每一个结点另开一个数组
l
,表示当前每个结点属于哪个字符串
同时记录结点在多少个字符串中出现过:
在构建完结点之后,我们从
now
开始往
parent
上跳,维护
l
和
pre=now;
for (;pre&&l[pre]!=id;pre=fa[pre])
{
l[pre]=id; ++cnt[pre];
}
不要忘了在复制结点的时候也要复制 cnt 和 l 的信息
l[nows]=l[q]; cnt[nows]=cnt[q];
tip
学姐表示:
这题AC自动机也是很好做的
把所有的查询串扔到
然后把模板串在自动机上匹配,每一次匹配到的所有点到根的路径上的所有查询串的
end
的答案都应该+1,这样区间修改单点查询
其实可以转化成单点修改然后转化为求子树权值和,并且因为每一次修改的点应该是一个并集,所以要按照
dfs
序排序然后容斥一下,不过是静态的最后只需要一次
dfs
就能出解了
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=100010;
int last=1,root=1,sz=1,len,ans;
int ch[N<<1][130],fa[N<<1],cnt[N<<1],dis[N<<1],l[N<<1];
char s[N];
void insert(int x,int id)
{
int now=++sz,pre=last;
last=now;
dis[now]=dis[pre]+1;
for (;pre&&!ch[pre][x];pre=fa[pre]) ch[pre][x]=now;
if (!pre) fa[now]=root;
else
{
int q=ch[pre][x];
if (dis[q]==dis[pre]+1) fa[now]=q;
else
{
int nows=++sz;
dis[nows]=dis[pre]+1;
memcpy(ch[nows],ch[q],sizeof(ch[q]));
l[nows]=l[q]; cnt[nows]=cnt[q];
fa[nows]=fa[q]; fa[q]=fa[now]=nows;
for (;pre&&ch[pre][x]==q;pre=fa[pre]) ch[pre][x]=nows;
}
}
pre=now;
for (;pre&&l[pre]!=id;pre=fa[pre])
{
l[pre]=id; ++cnt[pre];
}
}
int solve()
{
len=strlen(s+1);
int now=root;
for (int i=1;i<=len;i++)
{
int x=s[i];
if (!ch[now][x]) return 0;
now=ch[now][x];
}
return cnt[now];
}
int n,Q;
int main()
{
scanf("%d%d",&n,&Q);
for (int i=1;i<=n;i++)
{
scanf("%s",s+1);
len=strlen(s+1); last=root;
for (int j=1;j<=len;j++) insert(s[j],i);
}
for (int i=1;i<=Q;i++)
{
scanf("%s",s+1);
printf("%d\n",solve());
}
return 0;
}