刚开始想当然的以为是求单词们出现了多少次,事实上是有多少单词出现了
其实,理解了,写这个不难
三步走
1:加单词建Trie
2:get fail和 last数组
3:套文本开始查找,延失配边走即可
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
using namespace std;
int n,m,ch[500005][30],val[500005],tot,fail[500005],last[500005];
char wd[55],s[1020000];
bool vis[500005];
inline int idx(char a) {return a-'a';}
void insert(char *s,int len)
{
int k=0;
for (int i=0;i<len;i++)
{
int id=idx(s[i]);
if (ch[k][id]==0) ch[k][id]=++tot;
k=ch[k][id];
}
val[k]++;
}
void getfail()//获取fail和last(fail当中有用的部分)数组
{
queue<int> q;
for (int i=0;i<26;i++)
if (ch[0][i])
{
q.push(ch[0][i]);
fail[ch[0][i]]=last[ch[0][i]]=0;
}
while (!q.empty())
{
int u=q.front(),v;q.pop();
for (int i=0;i<26;i++)
if (v=ch[u][i])
{
q.push(v);
int k=fail[u];
while (!ch[k][i]&&k) k=fail[k];
if (ch[k][i]) fail[v]=ch[k][i];
if (val[fail[v]]) last[v]=fail[v];
else last[v]=last[fail[v]];//如果它失配的那个点有价值,那么等于他,否则等于失配点所对的那个边
}
}
}
int find(char *s,int len)
{
int ans=0,j=0;
for (int i=0;i<len;i++)
{
int id=idx(s[i]);
while (!ch[j][id]&&j) j=fail[j];
if (ch[j][id]) j=ch[j][id];
int k=j;
while (!vis[k]&&k)//只要这里没有被访问过,且不是原点就继续
{
ans+=val[k];//加上这里的单词数
vis[k]=true;//置为已访问
k=last[k];//沿着后缀链接走
}
}
return ans;
}
void work()
{
tot=0;
memset(ch,0,sizeof(ch));
memset(val,0,sizeof(val));
memset(fail,0,sizeof(fail));
memset(last,0,sizeof(last));
memset(vis,false,sizeof(vis));
//init
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%s",wd);
insert(wd,strlen(wd));//插入单词
}
getfail();//获取fail和last数组
scanf("%s",s);
printf("%d\n",find(s,strlen(s)));//查找
}
int main()
{
int T;
scanf("%d",&T);
while (T--) work();
return 0;
}