Bzoj3172——单词(AC自动机)

3172: [Tjoi2013]单词

Time Limit: 10 Sec   Memory Limit: 512 MB
Submit: 5051   Solved: 2467

Description

某人读论文,一篇论文是由许多单词组成。但他发现一个单词会在论文中出现很多次,现在想知道每个单词分别在论文中出现多少次。

Input

第一个一个整数N,表示有多少个单词,接下来N行每行一个单词。每个单词由小写字母组成,N<=200,单词长度不超过10^6

Output

输出N个整数,第i行的数字表示第i个单词在文章中出现了多少次。

Sample Input


3

a

aa

aaa

Sample Output


6

3

1


题解

暴力算法:
以每个词为文本串做匹配,每匹配上一个位置,就从该节点延fail或last数组上溯,给经过的的词尾结点加上1次出现次数

优化:
由上述算法可知,每个文本串(即每个单词)在AC自动机上的每个结点,都可以使 其延fail数组能走到的单词 的出现次数加1
因此,可以建出fail树,给每个单词在AC自动机上的每个结点标号都加1(打标记),意味着其父结点中词尾的出现次数增加1
但这里不上溯,在fail树上做一次dp(类似前缀和)
这里可以直接利用建立AC自动机时的队列,从后往前,每个元素对应fail树中的结点层数一定是从高到低的

处理重复单词:
若单词i与j相同 (i<j) ( i < j ) ,就用链表把j接到i的后面,AC自动机的词尾结点上记录该词第一次出现的序号,即可

代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdlib>
using namespace std;
const int N=1000005;
int ch[N][30],val[N],lastnum[N],sum[N],q[N],tot=0,ans[N],f[N];
char st[N];
void insert(int t)
{
    int u=0,n=strlen(st);
    for (int i=0; i<n; i++){
        int c=st[i]-'a';
        if (!ch[u][c]) ch[u][c]=++tot;
        u=ch[u][c];
        sum[u]++;
    }
    if (val[u]==0) val[u]=lastnum[t]=t;
    else lastnum[t]=val[u];
}
void pre()
{
    int head=0,tail=0; 
    for (int i=0; i<26; i++) if (ch[0][i]>0) q[tail++]=ch[0][i];
    while (head<tail){
        for (int i=0; i<26; i++)
            if(ch[q[head]][i]>0){
                q[tail++]=ch[q[head]][i];
                int u=f[q[head]];
                while(u>0 && ch[u][i]==0) u=f[u];
                f[ch[q[head]][i]]=ch[u][i]; 
            }
        head++;
    }
    for (int i=tail-1; i>=0; i--){
        if (val[q[i]]>0) ans[val[q[i]]]=sum[q[i]];
        sum[f[q[i]]]+=sum[q[i]];
    }
}
int main()
{
    int n;
    scanf("%d",&n);
    for (int i=1; i<=n; i++) scanf("%s",st),insert(i);
    pre();
    for (int i=1; i<=n; i++) printf("%d\n",ans[lastnum[i]]);
    return 0;
}

miao~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值