[省选前题目整理][BZOJ 3172][TJOI 2013]单词(AC自动机+fail树)

142 篇文章 0 订阅
98 篇文章 0 订阅

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=3172

思路

首先将所有单词都插入AC自动机的Trie树中,记录下每个单词的危险节点(最后一个字符对应的节点)在Trie树中的编号。可以把Trie树中每个结点的fail指针看成一条边,fail指针指向的节点看成新树中这个节点的父亲,这就构成了fail树。我们记录下每个结点i在插入单词过程中被访问的次数f[i],那么在fail树中,对于每个点u及其儿子v而言,f[u]=f[u]+∑f[vi],vi是u的儿子(u代表文章的一个前缀,其fail指针所指向的点代表这个前缀的一个后缀,显然这个后缀的出现次数要算上它所在的所有前缀的出现次数)。那么单词i的出现次数就是i的危险节点的f值。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1000000

using namespace std;

int pos[MAXN]; //pos[i]=编号为i的单词的危险结点在trie中的编号

struct Trie
{
    int ch[26],fail;
    int cnt; //该节点在匹配时被遍历过的次数
}node[MAXN];

int nCount=0;

void Insert(char *s,int id) //将编号为id的串s插入trie
{
    int p=0;
    while(*s!='\0')
    {
        if(!node[p].ch[*s-'a'])
            node[p].ch[*s-'a']=++nCount;
        p=node[p].ch[*s-'a'];
        s++;
        node[p].cnt++;
    }
    pos[id]=p;
}

int n; //n个单词
char word[MAXN];

void init()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",word);
        Insert(word,i);
    }
}

int q[MAXN],h=0,t=0;

void buildAC()
{
    for(int i=0;i<26;i++)
        if(node[0].ch[i])
        {
            node[node[0].ch[i]].fail=0;
            q[t++]=node[0].ch[i];
        }
    while(h<t)
    {
        int now=q[h++];
        for(int i=0;i<26;i++)
        {
            if(node[now].ch[i])
            {
                int tmp=node[now].fail;
                while(tmp!=0&&!node[tmp].ch[i])
                    tmp=node[tmp].fail;
                if(node[tmp].ch[i])
                    tmp=node[tmp].ch[i];
                node[node[now].ch[i]].fail=tmp;
                q[t++]=node[now].ch[i];
            }
        }
    }
}

void solve()
{
    while(t)
    {
        t--;
        node[node[q[t]].fail].cnt+=node[q[t]].cnt;
    }
}

int main()
{
    init();
    buildAC();
    solve();
    for(int i=1;i<=n;i++)
        printf("%d\n",node[pos[i]].cnt);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值