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

题目

传送门

题解

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

代码

AC自动机:

#include  
#include 
#include 
#include 
#include 
#include 
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; iif (!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 
#include 
#include 
#include 
#include 
#include 
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; iif (!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; iif (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、后缀数组的写法,以后再学吧

你可能感兴趣的:(后缀数组,AC自动机)