想知道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
#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;
}
umm我想了下,把几个板子题都放这儿好了QAQ
这是板子2号
这题差不多啊,就先把trie树建起来,fail按套路求一下,然后注意一下的是计数的时候有个小技巧,就是可以让是结尾的节点的end=单词号,非结尾的=0,然后每次在文本串中扫到的时候就直接as[end]++就好了,没了
哦还有一个,,,题解第二个的方法似乎很妙,一个优化,一个树上dp,有时间再搞,QAQ
放下代码QAQ
#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;
}
然后放下被安利的题目:
阿最后说一下,还有一个小优化叫$last$优化,就将$fail$再优化了下,复杂度是没变的但实际上常有奇效$QwQ$