哈哈哈哈我学明白了也许吧。挂几篇题解:
b站的这个up讲得太好啦:https://www.bilibili.com/video/BV1uJ411Y7Eg?p=3&vd_source=f51708a79de2172437e75278f8c832e0

(https://blog.csdn.net/bestsort/article/details/82947639)与(https://www.cnblogs.com/cjyyb/p/7196308.html)呃还有(https://oi-wiki.org/string/ac-automaton/)这三个。
就是一种对字典树的优化吧,差不多咯,步骤呢也不多,不过前置知识是kmp与trie树(其实kmp还行,不是很行),第一步,先建一颗trie,然后捏,就做失配指针,失配指针是什么捏,就是呀,那个那个如果自己无法继续向下匹配了,那就只能换一部分去向下去问啦,比如说

对于一张这样的(抄的)图,如果你有一个sher的匹配串串,那你肯定要先左右左走到she那,然后你发现,后面没r了,那怎么办,那就换一边呗,用大佬的话说:Trie树的失配指针是指向是沿着其父节点 的 失配指针,一直向上,直到找到拥有当前这个字母的子节点 的节点 的那个子节点。所以我们可以换到最右链,her的,不过呢两个问题,1是第二层的节点的指针要手动打,第二就是,不存在的点也需要失配指针来维护。
或者我们可以将失配指针指向的的节点理解为:
当前节点所代表的串,最长的、能与后缀匹配的,在Trie中出现过的前缀所代表的节点。所以,fail指针类似于kmp的next数组,只不过由单串变为了多串而已
我自己的理解,便是,你要让最后那位是当前的这个字符嘛,后面的改不了,那就只能删点前面的咯,然后就一步步的删,向上缩嘛,但是要它尽量长啊对吧,所以就一层一层的向上咯重要的是
代码(大概是不知道从哪里学来的):

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
using namespace std;
struct Tree//字典树
{int fail;//失配指针int vis[26];//子节点的位置int end;//标记有几个单词以这个节点结尾
}AC[1000000];//Trie树
int cnt=0;//Trie的指针
inline void Build(string s)
{int l=s.length();int now=0;//字典树的当前指针 for(int i=0;i<l;++i)//构造Trie树{if(AC[now].vis[s[i]-'a']==0)//Trie树没有这个子节点AC[now].vis[s[i]-'a']=++cnt;//构造出来now=AC[now].vis[s[i]-'a'];//向下构造 }AC[now].end+=1;//标记单词结尾
}
void Get_fail()//构造fail指针
{queue<int> Q;//队列 for(int i=0;i<26;++i)//第二层的fail指针提前处理一下{if(AC[0].vis[i]!=0){AC[AC[0].vis[i]].fail=0;//指向根节点Q.push(AC[0].vis[i]);//压入队列 }}while(!Q.empty())//BFS求fail指针 {int u=Q.front();Q.pop();for(int i=0;i<26;++i)//枚举所有子节点{if(AC[u].vis[i]!=0)//存在这个子节点{AC[AC[u].vis[i]].fail=AC[AC[u].fail].vis[i];//子节点的fail指针指向当前节点的//fail指针所指向的节点的相同子节点 Q.push(AC[u].vis[i]);//压入队列 }else//不存在这个子节点 AC[u].vis[i]=AC[AC[u].fail].vis[i];//当前节点的这个子节点指向当//前节点fail指针的这个子节点 }}
}
int AC_Query(string s)//AC自动机匹配
{int l=s.length();int now=0,ans=0;for(int i=0;i<l;++i){now=AC[now].vis[s[i]-'a'];//向下一层for(int t=now;t&&AC[t].end!=-1;t=AC[t].fail)//循环求解{ans+=AC[t].end;AC[t].end=-1;//防止解被重复使用} }return ans;
}
int main()
{int n;string s;cin>>n;for(int i=1;i<=n;++i){cin>>s;Build(s);}AC[0].fail=0;//结束标志 Get_fail();//求出失配指针cin>>s;//文本串 cout<<AC_Query(s)<<endl;return 0;
}

自己的:

#include<bits/stdc++.h>
using namespace std;
int n,m;
struct tr
{int zz;int son[30];int sum;
};
tr ac[5000000];
int tot=0;
void bt(char p[])
{int len=strlen(p+1);int now=0;for(int i=1;i<=len;i++){int x=p[i]-'a'+1;if(ac[now].son[x]==0) ac[now].son[x]=++tot;now=ac[now].son[x];}ac[now].sum++;return ;
}
void zz()
{queue<int> q;for(int i=1;i<=26;i++){if(ac[0].son[i]!=0) {ac[ac[0].son[i]].zz=0;q.push(ac[0].son[i]);}}while(q.size()!=0){int x=q.front();q.pop();for(int i=1;i<=26;i++) {if(ac[x].son[i]!=0){ac[ac[x].son[i]].zz=ac[ac[x].zz].son[i];q.push(ac[x].son[i]);}else ac[x].son[i]=ac[ac[x].zz].son[i];}}
}
int find(char p[])
{int len=strlen(p+1);int now=0,ans=0;for(int j=1;j<=len;j++){int x=p[j]-'a'+1;now=ac[now].son[x];for(int i=now;i>0&&ac[i].sum!=-1;i=ac[i].zz){ans+=ac[i].sum;ac[i].sum=-1;}}return ans;
}
char s[1000001];
int main()
{tot=0;scanf("%d",&n);for(int i=1;i<=n;i++) {scanf("%s",s+1);bt(s);}ac[0].zz=0;zz();scanf("%s",s+1);printf("%d\n",find(s));return 0;
}

P3796 【模板】AC 自动机(加强版)这道和上面板子差不多啦,
不过还是有些细节什么的但不管咯,代码如下:

 #include<bits/stdc++.h>using namespace std;int n,m;int tot=0;char s[160][1001];int ll[10001];struct TREE{int fail,vis[27],sum;};TREE ac[100001];char ss[1000021];void bt(char a[],int len,int num){int now=0;for(int i=1;i<=len;i++){if(ac[now].vis[a[i]-'a'+1]==0) ac[now].vis[a[i]-'a'+1]=++tot;now=ac[now].vis[a[i]-'a'+1];}ac[now].sum=num;return ;}int q[1000001];void getfail(){int st=1,ed=1;for(int i=1;i<=26;i++) {if(ac[0].vis[i]!=0) {ac[ac[0].vis[i]].fail=0;q[ed]=ac[0].vis[i];ed++;}}while(st!=ed){int x=q[st];for(int i=1;i<=26;i++){if(ac[x].vis[i]!=0) {ac[ac[x].vis[i]].fail=ac[ac[x].fail].vis[i];q[ed]=ac[x].vis[i];ed++;
//                  if(ed>100000) ed=1;}else ac[x].vis[i]=ac[ac[x].fail].vis[i];           }st++;
//          if(st>100000) st=1;}return ;}struct node{int num,pos;} ans[1000001];void find(char p[],int len){int now=0;for(int i=1;i<=len;i++){now=ac[now].vis[p[i]-'a'+1];for(int t=now;t;t=ac[t].fail) ans[ac[t].sum].num++;}return ;}bool cmp(const node &x,const node &y){if(x.num!=y.num) return x.num>y.num;return x.pos<y.pos;}int main(){while(scanf("%d",&n),n!=0){tot=0;memset(ans,0,sizeof(ans));memset(ac,0,sizeof(ac));for(int i=1;i<=n;i++){scanf("%s",s[i]+1); ans[i].pos=i,ans[i].num=0;ll[i]=strlen(s[i]+1);bt(s[i],ll[i],i);}ac[0].fail=0;getfail();scanf("%s",ss+1); int len=strlen(ss+1);find(ss,len);sort(ans+1,ans+n+1,cmp);printf("%d\n",ans[1].num);for(int i=1;i<=ll[ans[1].pos];i++) printf("%c",s[ans[1].pos][i]);int k=2;while(ans[1].num==ans[k].num){printf("\n");for(int i=1;i<=ll[ans[k].pos];i++) printf("%c",s[ans[k].pos][i]);k++;}printf("\n");}return 0;}

烦死了,调了一下午发现主函数里忘getfail()了nmd!然后这个卡范围还是挺麻烦的。刚刚又发现一篇不错的bk(https://ouuan.github.io/post/ac%E8%87%AA%E5%8A%A8%E6%9C%BA%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/)。
嗯,第一题呀:P3966 [TJOI2013]单词呃,打完之后我对其fail的理解加深一百倍!就是说首先,我们都知道fail树就是在跑ac机中把失配指针所指向的值与失配指针所表示的边重新建出来的树。有这个思想,那么这道题就很简单了。
跑一遍AC自动机,每一个节点保存一下属于多少字符串,为它的权值。然后一个节点表示的字符串在整个字典中出现的次数相当于其在Fail树中的子树的权值的和。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int tot=0;
char s[2000001];
int wz[2000001];
struct trr
{int son[30],sum,fail;
};
trr ac[2000001];
int sz[2000001];
void bt(char a[],int len,int w)
{int now=0;for(int i=1;i<=len;i++){int x=a[i]-'a'+1;if(ac[now].son[x]==0) ac[now].son[x]=++tot;now=ac[now].son[x];    sz[now]++;} wz[w]=now;   return ;
}
int q[2000001];
void getfail()
{int st=1,ed=1;ac[0].fail=0;for(int i=1;i<=26;i++){if(ac[0].son[i]!=0) q[ed++]=ac[0].son[i];}while(st!=ed){int now=q[st];for(int i=1;i<=26;i++){if(ac[now].son[i]!=0){ac[ac[now].son[i]].fail=ac[ac[now].fail].son[i];q[ed++]=ac[now].son[i];//同时也用来排序,来控制串串的那个位置,怎么说捏,就是从后往前了?感性理解。}else ac[now].son[i]=ac[ac[now].fail].son[i];}st++;}
}
int main()
{scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%s",s+1);int len=strlen(s+1);bt(s,len,i);}   getfail();//前面都可以正常维护for(int i=tot;i>=1;i--) sz[ac[q[i]].fail]+=sz[q[i]];//就是这神奇的一句啦,意思是后面有的前面也有,所以便可以继承过去嘿嘿,理解理解for(int i=1;i<=n;i++) printf("%d\n",sz[wz[i]]);return 0;
}

下一个就是类似于那个kmp加栈的那玩意,P3121 [USACO15FEB]Censoring G
直接看我维护吧主要是多了一个深度要处理

#include<bits/stdc++.h>
using namespace std;
int n,m;
int tot=0;
int q[1000001];
char ans[1000001];
int dep[1000001];
struct trr
{int son[30],fail,sum;
};
trr ac[1000001];
void bt(char a[],int len)
{int now=0;for(int i=1;i<=len;i++){int x=a[i]-'a'+1;if(ac[now].son[x]==0) ac[now].son[x]=++tot;now=ac[now].son[x]; }ac[now].sum=1;return ;
}
void getfail()
{int st=1,ed=1;ac[0].fail=0;for(int i=1;i<=26;i++){if(ac[0].son[i]!=0) {dep[ac[0].son[i]]=1;//初始化第二层的深度,其实就是字符串长度啦ac[ac[0].son[i]].fail=0;q[ed++]=ac[0].son[i];}}while(st!=ed){int now=q[st];for(int   i=1;i<=26;i++){if(ac[now].son[i]!=0){ac[ac[now].son[i]].fail=ac[ac[now].fail].son[i];q[ed++]=ac[now].son[i];dep[ac[now].son[i]]=dep[now]+1;   //继承它一手~}else ac[now].son[i]=ac[ac[now].fail].son[i];}st++;}return ;
}
int r=0;
void find(char s[],int len)//直接拿栈
{int now=0;q[0]=0;for(int i=1;i<=len;i++){int x=s[i]-'a'+1;now=ac[now].son[x];q[++r]=now;ans[r]=s[i];if(ac[now].sum) r-=dep[now],now=q[r];//减去字符串的长度部分啦}
}
int main()
{char s1[1000001],s2[1000001];scanf("%s",s1+1);scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%s",s2+1);int len=strlen(s2+1);bt(s2,len);}getfail();int len=strlen(s1+1);find(s1,len);for(int i=1;i<=r;i++) printf("%c",ans[i]);return 0;
}

下一个是——P3041 [USACO12JAN]Video Game G题目先生!不过好像每次看ac自动机题解的时候都能看到yyb大佬的题解,太巨!呃其实这题就是一道ac自动机+dp嘛,转移很显然从Trie树的节点跳到他的儿子节点于是f [ i ] [ j ]表示长度为i,当前在 ac树 上的j位置时的最大得分,在求fail的时候可以完成值的累加咯, 最后一步就是转移啦。但是要注意一个问题,在计算的时候,每一个节点加入后能够造成的贡献,要加上他的子串的贡献。好代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,k;
struct trr
{int son[4],fail,sum;
};
trr ac[100001];
int tot=0,ans=0;
void bt(char s[],int len)
{int now=0;for(int i=1;i<=len;i++){int x=s[i]-'A'+1;if(ac[now].son[x]==0) ac[now].son[x]=++tot;    now=ac[now].son[x];    } ac[now].sum++;return ;
}
int q[100001];
void getfail()
{int st=1,ed=1;ac[0].fail=0;for(int i=1;i<=3;i++) {if(ac[0].son[i]!=0) {ac[ac[0].son[i]].fail=0;q[ed++]=ac[0].son[i];}}  while(st!=ed){int now=q[st];for(int i=1;i<=3;i++){if(ac[now].son[i]!=0){ac[ac[now].son[i]].fail=ac[ac[now].fail].son[i];q[ed++]=ac[now].son[i];}else ac[now].son[i]=ac[ac[now].fail].son[i];}ac[now].sum+=ac[ac[now].fail].sum;//极其高明的一句转移,将前面能和自己匹配的那个串串的值转移到自己身上 st++;}return ;
}
int f[2000][1001];
void dp()//精华呀我的妈,就是继承转移与dp~
{for(int len=0;len<=k;len++)//长度也可能为0呀qwq {for(int i=1;i<=tot;i++) f[len][i]=-1e9;//当然是最小啦 }for(int len=1;len<=k;len++){for(int i=0;i<=tot;i++){for(int j=1;j<=3;j++){f[len][ac[i].son[j]]=max(f[len][ac[i].son[j]],f[len-1][i]+ac[ac[i].son[j]].sum);//我一个儿子的最大值当然是我的值加上它(累加完)的值啦,注意len的定义哦 }}}for(int i=0;i<=tot;i++) ans=max(ans,f[k][i]);return ;
}
char s[100001];
int main()
{scanf("%d%d",&n,&k);for(int i=1;i<=n;i++){scanf("%s",s+1);int len=strlen(s+1);bt(s,len);}getfail();dp();printf("%d",ans);return 0;
}

下一题,有点小怀念,诶呀不说题外话!P4052 [JSOI2007]文本生成器,就说这题比较奇怪,呃采用正难则反的思路,算出总数是26^m,然后不合法的是不知道什么鬼,然后就可以知道合法了的啦,然后维护的方式就是建一颗ac树嘛,然后累加所有不在树上的节点就行啦。大概就是:

定义f[i][j]表示当前在j点且串长为i时不经过单词结尾的路径条数,然后从父亲往儿子转移即可
注意如果一个单词的后缀是一个可读单词(即fail指针指向可读单词的结尾节点),那么这个单词一定也是可读的,我们就不能往这个单词走了这个dp的理解确实深刻。
维护的话要特别讲一下的,就是使我对dp的理解更进一步,是什么捏,抄一下啊别人的

for(int i=1;i<=m;i++)//每一个点for(int j=1;j<=t.cnt;j++)//全点参与for(int c=1;c<=26;c++) //沿着子节点生成文本串下一个字母if(!t.mk[t.ch[j][c]])  //避开模式串dp[i][t.ch[j][c]]=(dp[i][t.ch[j][c]]+dp[i-1][j])%mod;//自己可以给儿子传多少,并且是上一层的自己才能给儿子传。//不过吐槽一句不知道能不能用滚动数组优化?。

有的人不能理解为什么这样能传递值啊,你先看看这一段

if(ac[now].son[i]!=0)
{ac[ac[now].son[i]].fail=ac[ac[now].fail].son[i];ac[ac[now].son[i]].sum|=ac[ac[ac[now].son[i]].fail].sum;//现在不说这句QWQq[ed++]=ac[now].son[i];
}
else ac[now].son[i]=ac[ac[now].fail].son[i];//如果我的儿子不存在,我将把给它的值,传到我fail的那个儿子上

所以本质上来说,它只是把传到虚空中的值给了那个存在的节点(
我的代码就来啦:

using namespace std;
int n,m;
struct trr
{int son[30],fail,sum;
};
trr ac[1000001];
int tot=0;
int f[200][100001];
void bt(char a[],int len)
{int now=0;for(int i=1;i<=len;i++){int x=a[i]-'A'+1;if(ac[now].son[x]==0) ac[now].son[x]=++tot;now=ac[now].son[x];}ac[now].sum=1;return ;
}
int q[1000001];
void getfail()
{int st=1,ed=1;ac[0].fail=0;for(int i=1;i<=26;i++){if(ac[0].son[i]!=0) {ac[ac[0].son[i]].fail=0;q[ed++]=ac[0].son[i]; }}while(st!=ed){int now=q[st];for(int i=1;i<=26;i++){if(ac[now].son[i]!=0) {ac[ac[now].son[i]].fail=ac[ac[now].fail].son[i];ac[ac[now].son[i]].sum|=ac[ac[ac[now].son[i]].fail].sum;//判断与自己后缀相同的那个串有解否然后就看看能不能继承咯q[ed++]=ac[now].son[i];}else ac[now].son[i]=ac[ac[now].fail].son[i];}st++;}
}
int ksm(int x,int c)
{int rt=1;while(c>0){if(c%2==1) rt=(rt*x)%10007;x*=x;x%=10007;c=c>>1;}return rt;
}
char s[100001];
int main()
{scanf("%d%d",&n,&m);for(int i=1;i<=n;i++){scanf("%s",s+1);int len=strlen(s+1);bt(s,len);}getfail();f[0][0]=1;//初始化极其重要for(int i=1;i<=m;i++)//因为初始化的缘故所以不会多算{for(int j=0;j<=tot;j++)//包括根节点也要算啊因为其实算的是它的儿子{for(int k=1;k<=26;k++){if(!ac[ac[j].son[k]].sum) f[i][ac[j].son[k]]=(f[i][ac[j].son[k]]+f[i-1][j])%10007;//向下传递咯(又或者向上?}}}int sum=ksm(26,m);for(int i=0;i<=tot;i++) sum=(sum-f[m][i]+10007)%10007;printf("%d",sum);//%10007 return 0;
}

下一道明天再补吧,估计难了。呜呜呜我还是含泪开始写了:CF163E e-Government感觉麻烦的要死,是ac自动机套树状数组(恶我已经抛弃线段树了嘛)不过还是恶心的一匹,难受死了那个空间与时间卡爆我,差一点没过呜呜呜,还好我开了o2不然就g了。
用别人的题解哈:不难发现,一个节点的 endend 为在它自身结尾的字符串数加上它在 fail 树上的祖先节点结尾的字符串树。所以,删去一个字符串就是找到它尾部字符所对应的节点,然后将 fail 树中此节点的子树上的每个点(也就是每个以此节点为祖先的节点)的 endend 值 −1。换句话说,删除和添加操作就是在一颗树上每次找一个节点,将它子树中所有点的权值加 1 或减 1。同理,询问操作就是在树上询问一个节点的权。在预处理出这棵树的 dfs 序后,操作就可以了区间修改、单点查询的裸线段树/树状数组维护。

说说我犯病的问题,可恶,我居然没看出它是差分,树状数组用来维护的是差分,所以xr是cnt+1。好好想想为什么可恶啊草。上代码:
这样的时间相对直接查询会更优一些啦。

#include<bits/stdc++.h>
#define lowbit(x) x&(-x)
//#define int long long
using namespace std;
int n,m;
int cnt,tot;
bool v[1000009];
int f[1000009];
int id[1000009];//保存那些有值的点
struct trr
{int son[30],fail,xl,xr;
};
trr ac[1000009];
int last[1000009];
struct pp
{int x,y,next;
};
pp p[1000009];
void add(int x,int y,int k)
{for(x;x<=cnt;x+=lowbit(x)) f[x]+=k;//明显的差分啊愚蠢bhfor(y;y<=cnt;y+=lowbit(y)) f[y]-=k;return ;
}
int find(int x)
{int rt=0;for(x;x>=1;x-=lowbit(x)) rt+=f[x];return rt;
}
void ins(int x,int y)
{int now=++cnt;p[now]={x,y,last[x]};last[x]=now;return ;
}
void bt(char s[],int len,int num)
{int now=0;for(int i=1;i<=len;i++){int x=s[i]-'a'+1;if(ac[now].son[x]==0) ac[now].son[x]=++tot;    now=ac[now].son[x];        }id[num]=now;return ;
}
int q[1000009];
void getfail()
{int st=1,ed=1;ac[0].fail=0;for(int i=1;i<=26;i++) {if(ac[0].son[i]!=0) {ac[ac[0].son[i]].fail=0;q[ed++]=ac[0].son[i];ins(0,ac[0].son[i]);}}while(st!=ed){int now=q[st];for(int i=1;i<=26;i++){if(ac[now].son[i]!=0){ac[ac[now].son[i]].fail=ac[ac[now].fail].son[i];ins(ac[ac[now].son[i]].fail,ac[now].son[i]);//顺便建个边啦 q[ed++]=ac[now].son[i]; }else ac[now].son[i]=ac[ac[now].fail].son[i]; }st++;}return ;
}
void dfs(int x)//处理出每个点的dfs序号以及其子树的其实用于搞那个树状数组
{//不过感觉这个树状数组的建法很奇怪欸,qw说是差分ac[x].xl=++cnt;for(int i=last[x];i!=-1;i=p[i].next) dfs(p[i].y);ac[x].xr=cnt+1;
}
char a[1000009];
int answer(char s[],int len)
{int now=0,rt=0;for(int i=2;i<=len;i++){now=ac[now].son[s[i]-'a'+1];rt+=find(ac[now].xl);    }return rt;
}
int main()
{memset(last,-1,sizeof(last));scanf("%d%d",&m,&n);for(int i=1;i<=n;i++){scanf("%s",a+1);int len=strlen(a+1);bt(a,len,i);}getfail();cnt=0;dfs(0);for(int i=1;i<=n;i++) add(ac[id[i]].xl,ac[id[i]].xr,1); memset(v,true,sizeof(v));for(int i=1;i<=m;i++){scanf("%s",a+1);int len=strlen(a+1);if(a[1]=='?') printf("%d\n",answer(a,len));else {int k=0;for(int i=2;i<=len;i++)     {k*=10;k+=a[i]-'0';}if(a[1]=='-'){if(v[k]==true) {v[k]=false;add(ac[id[k]].xl,ac[id[k]].xr,-1);}}else {if(v[k]==false){v[k]=true;add(ac[id[k]].xl,ac[id[k]].xr,1);}}}}return 0;
}

下一个题目是那个奇怪之题P2444 [POI2000]病毒话说要不是标签有我是真想不到的,不过题解说的好啊!呃呃呃ac自动机维护的是匹配的最多字符串嘛,那我们现在要没有,那我们就必须在字符串危险前用fail转移走嘛,那其实就可以建一个图,看看上面有没有环,如果有那就可以形成无限字符串啦,不过捏,注意要建边,可建边的时候不能建危险点的哈。
还是放一下别人的题解吧:

来一波逆向思维。假设我们构造出了一个无限长的安全代码,再拿到AC自动机上匹配,会发生什么?
没错,当我们一位一位地匹配的时候,我们会发现,永远都不会跳到某个病毒代码段结尾的位置(以后把这里称作危险节点,因为匹配到此处表明已经出现了某个病毒代码段),然后似乎会在自动机里永无止境地打转转。。。。。。
既然这个自动机又像一个图,那我们的问题不就变成了——在AC自动机(trie图)中寻找一个环,并且环上没有任何危险节点,并且还要注意,这个环能被根节点访问到(也就是说从根节点出发能在不经过危险节点的情况下走到到这个环,不然在模拟AC自动机匹配的时候无法到达这个这个环,也就失去了意义,楼上Dalao这里可能表述不尽准确)。

好像还有些注意事项,因为是有向边,所以就是判断环的时候要用两个数组,一个维护是否走过,另一个维护是否是路径上的点,这个问题想了一会,就只是用一个数组的话会有误判的情况的呀比如说一个 1 -> 2 ,1 -> 3 ,2 -> 4,3 -> 4的情况如果走完1到2到4后,再走1到3的话只用一个数组就会判定有环,所以要用另一个数组来记录已经走过的路径啦。

#include<bits/stdc++.h>
using namespace std;
int n,m;
int tot=0,len=0;
int pd1=-1,pd0=-1;
char a[600001];
struct TR
{int son[5],fail,sum;
};
TR ac[600001];
void bt(char s[],int len)
{int now=0;for(int i=1;i<=len;i++){int x=s[i]-'0';if(x==1) pd1=1;if(x==0) pd0=0;if(ac[now].son[x]==0) ac[now].son[x]=++tot;now=ac[now].son[x];}ac[now].sum=1;   return ;
}
int q[3000001];
void getfail()
{int st=1,ed=1;ac[0].fail=0;for(int i=0;i<=1;i++){if(ac[0].son[i]!=0) {ac[ac[0].son[i]].fail=0;q[ed++]=ac[0].son[i];}}while(st!=ed){int now=q[st];for(int i=0;i<=1;i++){if(ac[now].son[i]!=0){ac[ac[now].son[i]].fail=ac[ac[now].fail].son[i];q[ed++]=ac[now].son[i];ac[ac[now].son[i]].sum|=ac[ac[ac[now].son[i]].fail].sum;}else ac[now].son[i]=ac[ac[now].fail].son[i];}st++;}return ;
}
bool v[300001],ll[300001];
void dfs(int x)
{if(ll[x]) printf("TAK\n"),exit(0);if(v[x]||ac[x].sum!=0) return ;v[x]=true;ll[x]=true;for(int i=0;i<=1;i++){if(ac[x].son[i]) dfs(ac[x].son[i]);    }   ll[x]=false;return ;
}
int main()
{memset(ll,false,sizeof(ll));memset(v,false,sizeof(v));scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%s",a+1);int len=strlen(a+1);bt(a,len);}getfail();ac[0].sum=0;dfs(0);if(pd1!=1||pd0!=0) printf("TAK\n");else printf("NIE");return 0;
}

CF1207G Indie Album服了,这一题我搞了两小时不过,做完看起来的话不算难吧。我是铸币。你看对于一个s1+s2的串串,如果啊,就是能与t串匹配的话呢,那一定可以设置一段是s1,一段是s2嘛,所以我们就可以枚举断点,这是第一点咯,然后啊,我们单独匹配是可以算出来的嘛,所以呢合起来的也可以呀,https://www.cnblogs.com/alex-wei/p/CF1202E.html别人的博客?感觉可以然后捏,就是难说。呃呃呃呃感性理解一下吧当我的断点在某一位置的时候,就可以用乘法原理统计断点两端的数量,于是,问题本质上就是要对文本串的每个前缀求出有多少模式串是他的后缀,我们看对字符串如何处理,具体而言就是,我们可以将其翻转,(妙啊),然后呢,对模式串建AC自动机,考虑fail指针指向的是根到当前串的最长后缀。而所求等价于为模式串的后缀的数量,因此通过fail求和即可。嗯嗯就这样啦!

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m;
int tot[3]={0,0,0};
struct TR
{int son[30],fail,sum;
};
TR ac[300001][3];
void bt(char s[],int len,int k)
{int now=0;for(int i=1;i<=len;i++){int x=s[i]-'a'+1;if(ac[now][k].son[x]==0) ac[now][k].son[x]=++tot[k];now=ac[now][k].son[x];}ac[now][k].sum++;return ;
}
int q[300001];
void getfail()
{for(int k=0;k<=1;k++){int st=1,ed=1;for(int i=1;i<=26;i++){if(ac[0][k].son[i]!=0) {ac[ac[0][k].son[i]][k].fail=0;q[ed++]=ac[0][k].son[i];}    }while(st!=ed){int now=q[st];for(int i=1;i<=26;i++){if(ac[now][k].son[i]!=0){ac[ac[now][k].son[i]][k].fail=ac[ac[now][k].fail][k].son[i];ac[ac[now][k].son[i]][k].sum+=ac[ac[ac[now][k].son[i]][k].fail][k].sum;q[ed++]=ac[now][k].son[i];}else ac[now][k].son[i]=ac[ac[now][k].fail][k].son[i];}st++;}}return ;
}
int res[200001][3];
void find(char s[],int len,int k)
{int now=0;for(int i=1;i<=len;i++){int x=s[i]-'a'+1;now=ac[now][k].son[x];res[i][k]=ac[now][k].sum;}  return ;
}
char a1[200001],t[200001],a2[200001],tt[200001];
signed main()
{scanf("%s%lld",t+1,&n);for(int i=1;i<=n;i++){scanf("%s",a1+1);int len=strlen(a1+1),zz=len;for(int i=1;i<=len;i++) a2[zz--]=a1[i];bt(a1,len,0);bt(a2,len,1);}getfail();int len=strlen(t+1),len1=len;for(int i=1;i<=len;i++) tt[len1--]=t[i];find(t,len,0);find(tt,len,1);int ans=0;for(int i=1;i<len;i++) ans+=res[i][0]*res[len-i][1];//枚举正反串呗 printf("%lld",ans);return 0;
}

明天就下一题啦:总的来说鸽了很久其实蛮简单的,就不打了吧QWQ,

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define pii pair<int,int>
#define mp make_pair
#define ll long long
#define pb push_back
using namespace std;
const int MAX = 1e6+1000;
const int N = 1e6+1000;const int SIGMA_SIZE = 26;int ch[MAX][SIGMA_SIZE];
int f[MAX], sz, dfn[MAX], dfn_, out[MAX];
vector<int> to[MAX];void init() {dfn_ = 0;sz = 1;                         //节点个数memset(ch, 0, sizeof(ch));      //路径memset(f, 0, sizeof(f));        //失配指针
}int creat(char *s) {int u = 0, len = strlen(s);for (int i = 0; i < len; i++) {int c = s[i]-'a';if (!ch[u][c]) ch[u][c] = sz++;u = ch[u][c];}return u;
}void get_fail() {           //找失配指针queue<int> q;for (int i = 0; i < SIGMA_SIZE; i++)if (ch[0][i]) q.push(ch[0][i]);while (!q.empty()) {int r = q.front();q.pop();for (int c = 0; c < SIGMA_SIZE; c++) {int u = ch[r][c];if (!u) {ch[r][c] = ch[f[r]][c];continue;}q.push(u);int v = f[r];while (v && ch[v][c] == 0) v = f[v];f[u] = ch[v][c];}}
}
void Dfs(int u) {dfn[u] = ++dfn_;for(auto v:to[u])Dfs(v);out[u] = dfn_;
}
//---------------------------
int tree[N];
ll lb(ll x) {return x&-x;}
void add(ll pos, ll data) {for(int i = pos; i <= N-10; i += lb(i)) {tree[i] += data;}
}
ll query(ll pos) {ll ans = 0;for(int i = pos; i > 0 ; i -= lb(i)) {ans += tree[i];}return ans;
}
//----------------------
char str[N];
int ans[N];
int nxt[N][30];
int id[N],cnt;
vector<int> from[N];
int put(int &x) {if(x==-1) {cnt++;x = cnt;}return x;
}
vector<pii> que[N];
void dfs(int u,int sta) {add(dfn[sta],1);for(auto x:from[u])for(auto y:que[x])ans[y.second] = query(out[y.first])-query(dfn[y.first]-1);rep(i, 0, 25) {int v = nxt[u][i];if(v==-1) continue;dfs(v,ch[sta][i]);add(dfn[ch[sta][i]],-1);}
}
int main() {//freopen("a.txt","r",stdin);ios::sync_with_stdio(false);int n,m;memset(nxt,-1,sizeof(nxt));cin>>n;rep(i, 1, n) {int oper;cin>>oper;if(oper==1) {char a;cin>>a;id[i] = put(nxt[0][a-'a']);from[id[i]].pb(i);}else {int u;char a;cin>>u>>a;id[i] = put(nxt[id[u]][a-'a']);from[id[i]].pb(i);}}cin>>m;init();rep(i, 1 ,m) {int u;cin>>u>>str;que[u].pb(mp(creat(str),i));}get_fail();rep(i, 1, sz-1) to[f[i]].pb(i);Dfs(0);dfs(0,0);rep(i, 1, m) cout<<ans[i]<<endl;
}

ac自动机,自动ac机(bushi相关推荐

  1. 【AC自动机】AC自动机(二次加强版)(luogu 5357)

    正题 luogu 5357 题目大意 给你若干单词和一个字符串,让你查询每个单词在字符串中出现的次数 解题思路 AC自动机模板 先把单词丢进去,然后拿字符串去跑,每到一个点累计答案 因为数据较大,所以 ...

  2. AC自动机:多模式串匹配实现敏感词过滤

    文章出处:极客时间<数据结构和算法之美>-作者:王争.该系列文章是本人的学习笔记. 1 敏感词过滤场景 在很多支持用户发表内容的网站,都有敏感词过滤替换的功能.例如将一些淫秽.反动内容过滤 ...

  3. BZOJ.4820.[SDOI2017]硬币游戏(思路 高斯消元 哈希/AC自动机/KMP)

    BZOJ 洛谷 建出AC自动机,每个点向两个儿子连边,可以得到一张有向图.参照 [SDOI2012]走迷宫 可以得到一个\(Tarjan\)+高斯消元的\(O((nm)^3)\)的做法.(理论有\(6 ...

  4. ZOJ-3494 BCD Code (ac自动机+数位dp)

    题目链接 Problem Description Binary-coded decimal (BCD) is an encoding for decimal numbers in which each ...

  5. 提高篇 第二部分 字符串算法 第4章 AC自动机

    https://blog.csdn.net/wangyh1008/article/details/81428056 [模板]AC自动机(加强版) 洛谷3796 AC自动机_A_loud_name-CS ...

  6. KMP算法、AC自动机算法的原理介绍以及Python实现

    KMP算法 要弄懂AC自动机算法,首先弄清楚KMP算法. 这篇文章讲的很好: http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E ...

  7. UVA11019 Matrix Matcher【hash傻逼题】【AC自动机好题】

    LINK1 LINK2 题目大意 让你在一个大小为\(n*m\)的矩阵中找大小是\(x*y\)的矩阵的出现次数 思路1:Hash hash思路及其傻逼 你把一维情况扩展一下 一维是一个bas,那你二维 ...

  8. 2017 Multi-University Training Contest - Team 8:Fleet of the Eternal Throne(AC自动机)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6138 题目意思:题目给出n个字符串后,会有m个询问,每个询问会输入两个数,记为x,y吧. 然后要求的是 ...

  9. 【算法无用系列】AC自动机敏感词过滤

    简介: 本文是博主自身对AC自动机的原理的一些理解和看法,主要以举例的方式讲解,同时又配以相应的图片.代码实现部分也予以明确的注释,希望给大家不一样的感受.AC自动机主要用于多模式字符串的匹配,本质上 ...

  10. Python实现多模匹配——AC自动机

    Python实现多模匹配--AC自动机 目标:学习AC自动机,多模匹配. 要求:尽可能用纯Python实现,提升代码的扩展性. 一.什么是AC自动机? AC自动机,Aho-Corasick autom ...

最新文章

  1. 【Vegas原创】exp时,ORA-00932: 数据类型不一致解决方法
  2. python修改电脑名称_修改计算机名称
  3. 用hundred造句子_6分以上的人句子长啥样?
  4. 深入分析H2数据库控制台中无需身份验证的RCE漏洞
  5. android 扫描所有文件大小,Android获取指定文件大小
  6. 完善vim bccalc_linux插件
  7. android dip转px
  8. 从bsp redirect到ui5_ui5
  9. OrderAnalyticsController.initializeCachedDB - jdbc
  10. python 高级编程 豆瓣_python 的一些高级编程技巧
  11. bio、nio、aio及select、poll、epoll
  12. 小团队适合引入 Spring Cloud 微服务吗?
  13. JDK中IdentityHashMap使用详解
  14. Flash Remoting+ Visual Studio .NET学习总结
  15. 海思MPP venc 分析
  16. 第一章 数学建模与误差分析
  17. 测试脚本常用知识点python
  18. Oracle数据库的备份方式
  19. 利用GitHub Actions每天自动从Pixiv爬虫日推图片并存放到仓库
  20. 白帽子挖洞第II篇作业--xray+fofa主动扫描

热门文章

  1. jquery 编辑框foucs失效问题
  2. LeetCode 827 最大人工岛 题解
  3. 掌握函数的逆向思维竟然这么可怕!
  4. 图文并茂 —— 插入排序,希尔排序
  5. 无线耳机什么牌子的好?内行盘点四款好用的蓝牙耳机
  6. spider_day09
  7. 非root用户启动docker
  8. java数组赋值给js数组_java数组
  9. 【智能车】图像二值化算法--大津法OTSU
  10. 【java小程序实战】小程序注销功能实现