背景:
暑假集训七月份最后一天,写写原来没有写过的
blog
\text{blog}
blog吧。
题目传送门:
https://www.luogu.org/problemnew/show/P3966
题意:
给出
n
n
n个单词构成的文章,求每一个单词分别在文章中出现了多次。
思路:
一种显然的思路就是
AC
\text{AC}
AC自动机,记录每一个点分别属于那一些单词的
end
\text{end}
end,每一次暴力在
fail
\text{fail}
fail树上跳。
然而这样时间复杂度没法保证,只 有
90pts
\text{90pts}
90pts。
我们当然要考虑正解。
事实上,我们发现这个问题的本质就是统计
fail
\text{fail}
fail树的子树的大小(当然每一个加进来的点都要加上贡献)。
就可以
Θ
(
n
)
\Theta(n)
Θ(n)出解了。
暴力
KMP
\text{KMP}
KMP也能跑过,因为字符集的大小有限。
代码
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<algorithm>
using namespace std;
int n,len=0,st=0;
char s[1000010];
int end[210],ans[1000010],F[1000010];
queue<int> f;
struct node
{
int fail;
int son[30];
node()
{
fail=0;
memset(son,0,sizeof(son));
}
} tr[1000010];
void build(char *s,int id)
{
int l=strlen(s),x=0;
for(int i=0;i<l;i++)
{
if(!tr[x].son[s[i]-'a']) tr[x].son[s[i]-'a']=++len;
x=tr[x].son[s[i]-'a'];
ans[x]++;
}
end[id]=x;
}
void get_fail()
{
while(!f.empty()) f.pop();
for(int i=0;i<26;i++)
if(tr[0].son[i])
{
tr[tr[0].son[i]].fail=0;
f.push(tr[0].son[i]);
}
while(!f.empty())
{
int x=f.front();
F[++st]=x;
f.pop();
for(int i=0;i<26;i++)
{
int y=tr[x].son[i];
if(y)
{
tr[y].fail=tr[tr[x].fail].son[i];
f.push(y);
}
else tr[x].son[i]=tr[tr[x].fail].son[i];
}
}
}
void solve()
{
for(int i=len;i>=0;i--)
ans[tr[F[i]].fail]+=ans[F[i]];
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",s);
build(s,i);
}
tr[0].fail=0;
get_fail();
solve();
for(int i=1;i<=n;i++)
printf("%d\n",ans[end[i]]);
}