[BZOJ3172][Tjoi2013]单词(AC自动机)

题目

传送门

题解

AC自动机
AC自动机第一题,感觉做起来非常不顺,感觉难点在于处理单词重叠的问题
大体的思路应该是,我们把每个单词出现的字母在fail树上进行累加,然后建立bfs序,把单词的字母的ans累加到单词的第一个字母上(应该是这样吧?),输出答案;
另一种实现方法:这是我刚开始想到的,在每个单词之间添加一个'#' '#' 分隔组成文章,在处理重叠单词上:我们的mp[k]表示与第k个单词相同的最靠前的单词的编号,这样用于处理重叠的情况;
好像还是使用的链表,比较巧妙

代码

AC自动机:

#include <cstdio> 
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <string>
using namespace std;
const int maxn=2e6;
const int inf=1e9;

int n,pos[maxn],ans[maxn],bfsn[maxn];//bfs序 
struct Tree{
    int fail;
    int vis[30];
}AC[maxn];
string s[maxn];

int cnt,tot;
void build_AC(string s,int Num)
{
    int l=s.length(); int now=0;
    for (int i=0; i<l; i++)
    {
        if (!AC[now].vis[s[i]-'a'])
            AC[now].vis[s[i]-'a']=++cnt;
        now=AC[now].vis[s[i]-'a'];
        ans[now]++;
    }
    pos[Num]=now;//第Num个单词的结束位置 
}

queue <int> q;
void Get_fail()
{
    cnt=0;
    for (int i=0; i<26; i++)
    {
        if (AC[0].vis[i])//这里!=0 
        {
            AC[AC[0].vis[i]].fail=0;
            q.push(AC[0].vis[i]);
        }
    }
    while (!q.empty())
    {
        int now=q.front(); q.pop();
        bfsn[++cnt]=now;//建立bfs序,深度单调不下降 
        for (int i=0; i<26; i++)
        {
            if (AC[now].vis[i])
            {
                AC[AC[now].vis[i]].fail=AC[AC[now].fail].vis[i];
                q.push(AC[now].vis[i]);
            }
            else
            AC[now].vis[i]=AC[AC[now].fail].vis[i];
        }
    }
}

void getans()
{
    for (int i=cnt; i>=1; i--)
        ans[AC[bfsn[i]].fail]+=ans[bfsn[i]];
}

int main()
{
    scanf("%d",&n);
    for (int i=1; i<=n; i++)
    {
        cin>>s[i];
        build_AC(s[i],i);
    }
    Get_fail();
    getans();
    for (int i=1; i<=n; i++)
        printf("%d\n",ans[pos[i]]);
    return 0;
}

第二种实现方法:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <string>
using namespace std;
#define ll long long
const int maxn=2e6;
const int inf=1e9;

struct Tree{
    int fail;
    int vis[30];
}AC[maxn];
int n,all,last[maxn],next[maxn],mp[maxn],ans[maxn],head[maxn];
//next[i]表示第i个位置应该对应哪个最靠前的单词编号 mp[k]表示与第k个单词相同的最靠前的单词的编号,next、mp有一定的联系 
//last[i]是当前位置的上一个位置 last归根到底是一个指针 
string SS,s;

int cnt=0;
void build_AC(string s,int Num)
{
    int now=0; int l=s.length();
    SS+=s+'#'; all+=l+1;
    for (int i=0; i<l; i++)
    {
        if (!AC[now].vis[s[i]-'a']) AC[now].vis[s[i]-'a']=++cnt;
        now=AC[now].vis[s[i]-'a'];
    }
    if (!next[now]) next[now]=Num;
    mp[Num]=next[now];
}

queue <int> q;
void Get_fail()
{
    for (int i=0; i<26; i++)
    {
        if (AC[0].vis[i])
            q.push(AC[0].vis[i]);
    }
    while (!q.empty())
    {
        int now=q.front(); q.pop();
        for (int i=0; i<26; i++)
        {
            int to=AC[now].vis[i];
            if (to)
            {
                AC[to].fail=AC[AC[now].fail].vis[i];
                last[to] = next[AC[to].fail]?AC[to].fail:last[AC[to].fail];
                q.push(to);
            }
            else AC[now].vis[i]=AC[AC[now].fail].vis[i];
        }
    }
}

void ques()
{
    int now=0;
    for (int i=0; i<all; i++)
    {
        if (SS[i]=='#')
        {
            now=0; continue;
        }
        now=AC[now].vis[SS[i]-'a'];
        if (next[now]) ans[next[now]]++;
        int to=last[now];
        while (to)
        {
            ans[next[to]]++;
            to=last[to];
        }
    }
}
int main()
{
    scanf("%d",&n);
    for (int i=1; i<=n; i++)
    {
        cin>>s;
        build_AC(s,i);
    }
    Get_fail();
    ques();
    for (int i=1; i<=n; i++) printf("%d\n",ans[mp[i]]);
    return 0;
}

总结

if (!AC[now].vis[s[i]-'a']) AC[now].vis[s[i]-'a']=++cnt;没有写-'a'又浪费了好长时间!

这道题还有hash、后缀数组的写法,以后再学吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值