想知道484每个萌新oier在最初知道AC自动机的时候都会理解为自动AC稽什么的,,,反正我记得我当初刚知道这个东西的时候,我以为是什么神仙东西$hhhhh$

首先要学AC自动机,就要先学会俩知识点:

trie树和kmp(我记得我都写了学习笔记,,,然而写得太烂了不想放上来了,,,网上随便搜篇题解都写得比我好的样子TT

好的那就当做已经掌握了这俩了来学习AC自动机趴!

首先要知道AC自动机是解决什么东西的嘛QwQ

kmp是一对一嘛,就是说一个字符串匹配一个字符串

然后AC自动机就是解决它没有解决的问题——一对多,一个字符串匹配多个字符串

这方面的题目比较多问法什么的也比较多我就以板子题1为例学下这个知识点好了QAQ

首先我们就读入所有模式串,建一棵trie树

然后就建fail指针

先说说fail指针是干什么的趴QwQ

举个eg好了,假如模式串有ace acd say she shr her ced 然后文本串是aced

于是构出来的trie树长这样(,,,图咕了$QAQ$

假如我们现在再匹配ace,那就当匹配到e之后就没有辣,那我们就找有麻油还能匹配的呢

那我们就和之前想kmp的时候一样,想着怎么利用之前做了的事儿呢,就想到,假如我能匹配ace,我就一定也能匹配ce,就一定也能匹配e这样子的对趴

所以我们就找啊,找trie树上有麻油一个点它及它之前的字符串和ace的后缀相同的

这个就是fail指针的作用了——记录每个点的表示的字符串的最长后缀指向哪个点

明白了麻油!我jio得还挺好理解的!

哦对了我好像没有解释为什么是最长后缀,,,?其实我jio得挺显然的?就是因为假如我有个ac还有个a,那我如果指向ac之后等ac匹配完了自然会去a的,可是如果指向的是a它不可能再指向ac了

get?

好大概思路就是这样的,具体实现和代码等下再说趴,,,QAQ

AC自动机学习笔记!AC自动机学习笔记!
#include<bits/stdc++.h>
using namespace std;
#define ll int
#define rp(i,x,y) for(register ll i=x;i<=y;++i)

const ll N=1000002;
ll n,cnt,as;
struct tre{ll ed,nxt[27],fail;tre(){ed=0;memset(nxt,0,sizeof(nxt));fail=0;}}tr[N];

inline ll read()
{
    register char ch=getchar();register ll x=0;register bool y=1;
    while(ch!='-' && (ch>'9' || ch<'0'))ch=getchar();
    if(ch=='-')ch=getchar(),y=0;
    while(ch>='0' && ch<='9')x=(x<<1)+(x<<3)+(ch^'0'),ch=getchar();
    return y?x:-x;
}
inline void bd(string x)
{
    ll lth=x.length()-1,nwtr=0;
    rp(i,0,lth){if(tr[nwtr].nxt[x[i]-'a'+1]==0)tr[nwtr].nxt[x[i]-'a'+1]=++cnt;nwtr=tr[nwtr].nxt[x[i]-'a'+1];}
    ++tr[nwtr].ed;
}
inline void fl()
{
    queue<ll>Q;
    rp(i,1,26)if(tr[0].nxt[i])Q.push(tr[0].nxt[i]);
    while(!Q.empty())
    {
        ll nw=Q.front();Q.pop();
        rp(i,1,26)
        {
            if(tr[nw].nxt[i]){tr[tr[nw].nxt[i]].fail=tr[tr[nw].fail].nxt[i];Q.push(tr[nw].nxt[i]);}
            else tr[nw].nxt[i]=tr[tr[nw].fail].nxt[i];
        }
    }
}
inline void zdj(string str)
{
    ll lth=str.length()-1,nw=0;
    rp(i,0,lth)
    {
        nw=tr[nw].nxt[str[i]-'a'+1];
        for(ll t=nw;t && tr[t].ed!=-1;t=tr[t].fail)
        {
            as+=tr[t].ed;
            tr[t].ed=-1;
        } 
    }
    printf("%d\n",as);
}

int main()
{
    n=read();rp(i,1,n){string str;cin>>str;bd(str);}fl();
    string str;cin>>str;zdj(str);
    return 0;
}
这题要注意下,,,我只开大了点儿就MLE了QAQ!

 umm我想了下,把几个板子题都放这儿好了QAQ

这是板子2号

这题差不多啊,就先把trie树建起来,fail按套路求一下,然后注意一下的是计数的时候有个小技巧,就是可以让是结尾的节点的end=单词号,非结尾的=0,然后每次在文本串中扫到的时候就直接as[end]++就好了,没了

哦还有一个,,,题解第二个的方法似乎很妙,一个优化,一个树上dp,有时间再搞,QAQ

放下代码QAQ

AC自动机学习笔记!AC自动机学习笔记!
#include<bits/stdc++.h>
using namespace std;
#define ll int
#define rp(i,x,y) for(register ll i=x;i<=y;++i)

const ll N=1000002,M=160;
ll n,cnt,as;
struct tre
{
    ll ed,nxt[27],fail,cs;
    void clr(){ed=0;memset(nxt,0,sizeof(nxt));fail=0;}
}tr[N];
struct ans{ll pos,as;void clr(){as=0;pos=0;}}ass[M];
bool gdgs=1;
string str[M];

inline ll read()
{
    register char ch=getchar();register ll x=0;register bool y=1;
    while(ch!='-' && (ch>'9' || ch<'0'))ch=getchar();
    if(ch=='-')ch=getchar(),y=0;
    while(ch>='0' && ch<='9')x=(x<<1)+(x<<3)+(ch^'0'),ch=getchar();
    return y?x:-x;
}
inline bool cmp(ans gd,ans gs){return gd.as==gs.as?gd.pos<gs.pos:gd.as>gs.as;}
inline void bd(ll nm)
{
    cin>>str[nm];ass[nm].clr();ass[nm].pos=nm;ll lth=str[nm].length()-1,nw=0;
    rp(i,0,lth)
    {
        if(tr[nw].nxt[str[nm][i]-'a'+1]==0)tr[nw].nxt[str[nm][i]-'a'+1]=++cnt,tr[cnt].clr();
        nw=tr[nw].nxt[str[nm][i]-'a'+1];
    }
    tr[nw].ed=nm;
}
inline void fl()
{
    queue<ll>Q;
    rp(i,1,26)if(tr[0].nxt[i])Q.push(tr[0].nxt[i]);
    while(!Q.empty())
    {
        ll nw=Q.front();Q.pop();
        rp(i,1,26)
        {
            if(tr[nw].nxt[i])tr[tr[nw].nxt[i]].fail=tr[tr[nw].fail].nxt[i],Q.push(tr[nw].nxt[i]);
            else tr[nw].nxt[i]=tr[tr[nw].fail].nxt[i];
        }
    }
}
inline void zdj()
{
    string str;cin>>str;ll lth=str.length()-1,nw=0;
    rp(i,0,lth)
    {
        nw=tr[nw].nxt[str[i]-'a'+1];
        for(register ll j=nw;j;j=tr[j].fail)++ass[tr[j].ed].as;
    }
}

int main()
{
    while(gdgs)
    {
        n=read();if(!n)exit(0);tr[0].clr();cnt=0;rp(i,1,n)bd(i);fl();tr[0].fail=0;zdj();
        sort(ass+1,ass+n+1,cmp);printf("%d\n",ass[1].as);
        cout<<str[ass[1].pos]<<endl;rp(i,2,n)if(ass[i].as==ass[i-1].as)cout<<str[ass[i].pos]<<endl;else break;
    }
    return 0;
}
,,,玄学事件?我开始打的是for(i)里套个for(i)也过去辣QAQ?

然后放下被安利的题目:

病毒

阿狸的打字机

单词

最短母串问题

阿最后说一下,还有一个小优化叫$last$优化,就将$fail$再优化了下,复杂度是没变的但实际上常有奇效$QwQ$

相关文章: