题目描述
传送门
注意:题目有多组数据。
题解
之前写过这道题,但是感觉复杂度应该是
O(S∗len)
的,也就是
5∗107
换了一个更科学的做法。就是每一次匹配都把它的权值+1,然后最后再fail树上统计子树和。
用到的性质就是fail树上的节点到根路径上的点都是这个点表示的字符串的前缀的后缀。
这样的话时间复杂度应该是
O(S+N∗len)≈2.05∗106
代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
char virus[1005][55],s[2000005];
int n,sz;
int tot,point[50005],nxt[50005],v[50005];
int ch[50005][130],fail[50005],is_end[1005],size[50005],cnt[50005];
queue <int> q;
void clear()
{
sz=tot=0;
memset(point,0,sizeof(point));memset(nxt,0,sizeof(nxt));memset(v,0,sizeof(v));
memset(ch,0,sizeof(ch));memset(fail,0,sizeof(fail));memset(is_end,0,sizeof(is_end));
memset(size,0,sizeof(size));memset(cnt,0,sizeof(cnt));
}
void insert(int id,char s[])
{
int len=strlen(s),now=0;
for (int i=0;i<len;++i)
{
int x=s[i];
if (!ch[now][x]) ch[now][x]=++sz;
now=ch[now][x];
}
is_end[id]=now;
}
void make_fail()
{
while (!q.empty()) q.pop();
for (int i=0;i<=128;++i)
if (ch[0][i]) q.push(ch[0][i]);
while (!q.empty())
{
int now=q.front();q.pop();
for (int i=0;i<=128;++i)
{
if (!ch[now][i])
{
ch[now][i]=ch[fail[now]][i];
continue;
}
fail[ch[now][i]]=ch[fail[now]][i];
q.push(ch[now][i]);
}
}
}
void ac()
{
int len=strlen(s),now=0;
for (int i=0;i<len;++i)
{
int x=s[i];
int y=ch[now][x];
cnt[y]++;
now=y;
}
}
void add(int x,int y)
{
++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
}
void dfs(int x)
{
size[x]=cnt[x];
for (int i=point[x];i;i=nxt[i])
{
dfs(v[i]);
size[x]+=size[v[i]];
}
}
int main()
{
while (~scanf("%d\n",&n))
{
clear();
for (int i=1;i<=n;++i)
{
gets(virus[i]);
insert(i,virus[i]);
}
gets(s);
make_fail();
ac();
for (int i=1;i<=sz;++i) add(fail[i],i);
dfs(0);
for (int i=1;i<=n;++i)
if (size[is_end[i]])
printf("%s: %d\n",virus[i],size[is_end[i]]);
}
}