并不对劲的AC自动机
这像是能解决所有问题的样子(并不)。AC自动机之所以叫AC自动机是因为它能解决所有AC自动机的题。
其实只能解决的是很多模式串匹配一个母串的问题。
把kmp中的next数组得到下一次跳转的位置看成特殊的边,把字符串看成链,就会得到一个特殊的图。
一个点u的next连向点v对应的字符串是u最长的后缀,把这个图套用到很多个模式串组成的trie上。
对于点u能走到的节点ch[u][i],相当于在u对应的字符串后补了一个字符i。那么点u的next连向的点v对应的字符串后面再补一个字符i就是点ch[u][i]的next。有时并没有ch[v][i],那么就要找v的next也就是更短的后缀。要是一直找不到,就只能连向0号点(空串)了。算next是一定要用bfs,不然会出现要走u的next却发现u的next还没有被算出的尴尬情况。
匹配时,如果没有ch[u][i]就要走u的next。还要注意的是,能走到u说明也能走到u的next、u的next的next、u的next的next的next等,要把这些点的值也加上。这样不会TLE吗?想必是不会的。要想知道一堆字符串中有多少个是某个串的子串,走到每个字符串结尾代表的节点时,只能统计一次。那么在统计时可以进行标记,被标记的点就不走了。又因为当一个点被标记时,它的next、它的next的next等都已经被统计过了,所以当顺着next统计的时候,发现一个被标记的点就可以停止了。自动机上的每一个点只会走到一次,所以并不会TLE。
考虑next很麻烦?当ch[u][i]==0时,令ch[u][i]=next!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<algorithm> #include<cmath> #include<cstdio> #include<cstdlib> #include<cstring> #include<iomanip> #include<iostream> #include<map> #include<queue> #include<stack> #include<vector> #define maxn 1000010 #define rep(i,x,y) for(register int i=(x);i<=(y);i++) #define dwn(i,x,y) for(register int i=(x);i>=(y);i--) #define vv ch[u][i] using namespace std; inline int read() { int x=0,f=1; char ch=getchar(); while(isdigit(ch)==0 && ch!='-')ch=getchar(); if(ch=='-')f=-1,ch=getchar(); while(isdigit(ch))x=x*10+ch-'0',ch=getchar(); return x*f; } inline void write(int x) { int f=0;char ch[20]; if(!x){puts("0");return;} if(x<0){putchar('-');x=-x;} while(x)ch[++f]=x%10+'0',x/=10; while(f)putchar(ch[f--]); putchar('\n'); } int ch[maxn][30],val[maxn],fail[maxn],cnt=0,n,len; char s[maxn]; int gx(char c){return c-'a';} void build() { int u=0; rep(i,0,len-1) { if(!ch[u][gx(s[i])])ch[u][gx(s[i])]=++cnt; u=ch[u][gx(s[i])]; } val[u]++; } void getfail() { fail[0]=0; queue<int >q; rep(i,0,25) if(ch[0][i]) q.push(ch[0][i]), fail[ch[0][i]]=0; while(!q.empty()) { int u=q.front();q.pop(); rep(i,0,25) { if(vv) q.push(vv),fail[vv]=ch[fail[u]][i]; else vv=ch[fail[u]][i]; } } } int getans() { int ans=0,u=0; rep(i,0,len-1) { u=ch[u][gx(s[i])]; for(int j=u;j&&val[j]!=-1;j=fail[j]) ans+=val[j],val[j]=-1; } return ans; } int main() { n=read(); rep(i,1,n) scanf("%s",s),len=strlen(s),build(); getfail(); scanf("%s",s),len=strlen(s);write(getans()); return 0; }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<set> #include<map> #include<cmath> #include<queue> #include<stack> #include<ctime> #include<string> #include<cstdio> #include<vector> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define LL long long #define MAXN 1000+1000000 #define INF 2147483647 using namespace std; inline int read(){ int x=0,f=1; char ch=getchar(); for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=ch-'0'+(x<<3)+(x<<1); return x*f; } char str[MAXN]; struct ACAutomata{ int ch[MAXN][31],val[MAXN],size; queue<int>Q; int fail[MAXN],vis[MAXN]; void insert(){ int cur=0,len=strlen(str); for(int i=0;i<len;i++){ if(ch[cur][str[i]-'a'])cur=ch[cur][str[i]-'a']; else ch[cur][str[i]-'a']=++size,cur=size; } val[cur]++; return ; } void calc(){ while(!Q.empty()){ int u=Q.front(); Q.pop(); for(int i=0;i<26;i++){ if(!ch[u][i])continue; int cur=fail[u]; Q.push(ch[u][i]); while(!ch[cur][i]&&cur)cur=fail[cur]; fail[ch[u][i]]=ch[cur][i]; } } return ; } int solve(){ int cur=0,ans=0,len=strlen(str),pos=0; while(pos<len){ for(int i=cur;!vis[i]&&i!=0;i=fail[i]) ans+=val[i],vis[i]=1; if(ch[cur][str[pos]-'a'])cur=ch[cur][str[pos]-'a']; else { cur=fail[cur]; while(!ch[cur][str[pos]-'a']&&cur)cur=fail[cur]; cur=ch[cur][str[pos]-'a']; } for(int i=cur;!vis[i]&&i!=0;i=fail[i]) ans+=val[i],vis[i]=1; pos++; } return ans; } }T; int main(){ int n=read(); for(int i=1;i<=n;i++){scanf("%s",str);T.insert();} for(int i=0;i<=25;i++)if(T.ch[0][i])T.Q.push(T.ch[0][i]); T.calc(); scanf("%s",str); printf("%d",T.solve()); return 0; }
别人一般把这种并不对劲的next称为失配边、fail之类的。
刚刚说过这是一个特殊的图,因为每个点只会连出一条失配边,所有失配边也组成了一棵树。
这样树上的某些坑人操作也可以在AC自动机上做了。。。