AC 自动机学习笔记
和 KMP 的关系大概就是 SAM 和广义 SAM 的关系吧。
类似 KMP,现在考虑对一个 Trie 树构造一个自动机:
- 状态为 Trie 树自己,即 Trie 树包含的所有串的前缀。
- 对于每个节点,失配指针 \(\operatorname{fail}\) 指向其最长的匹配某个前缀的后缀。
考虑如何构建这个自动机。
对于加入一个节点 \(u\),考虑类似 KMP:
- 如果它父亲节点 \(fa\) 对应的失配指针 \(\operatorname{fail}_{fa}\) 的节点有一个字符为 \(c\) 的转移,显然 \(\operatorname{fail}_u\) 就是这个转移的节点。
- 否则继续跳 \(\operatorname{fail}\) 即可。
但是这样复杂度会有问题,比如说给你一个 \(\frac{n}{2}\) 的链下面挂着 \(\frac{n}{2}\) 个点,然后每个点都需要跳完,然后复杂度就炸了。
注意到某个状态转移到同一个字符 \(c\) 的转移是确定的,对于相同的字符就不用再重新做一遍了。
复杂度 \(O(n\vert \Sigma \vert)\)。
然后考虑如何计算 Trie 树上的一些子串在给定串 \(T\) 出现的次数。
和 KMP 类似,假设读进来一个 \(T_{1\sim i-1}\) 的前缀,我们已经记录了它能够匹配的最长的后缀在 Trie 上的节点编号 \(u\)。
然后类似的,假设下一个转移的字符是 \(c\):
- 如果 \(u\) 有 \(c\) 的转移,则新的 \(u\) 即它转移到 \(u\)。
- 否则,\(u\) 跳到 \(fail_u\) 处继续判断。
但是注意,虽然我们记录了一个最长的后缀,但可能存在其他能够匹配但不是最长的节点,这部分的答案就会被漏算。
也是类似 KMP 的,\(u\) 能够匹配的 \(\operatorname{fail}_u\) 也能匹配,所以对于每个 \(u\),我们需要将其跳 \(\operatorname{fail}\) 的这条链全部 \(+1\)。
P3966 [TJOI2013] 单词
这题比较特殊,因为匹配串和模式串是一样的,所以不会出现 \(u\) 跳 \(\operatorname{fail}\) 的操作。
#include<bits/stdc++.h>
#define sd std::
//#define int long long
#define F(i,a,b) for(int i=(a);i<=(b);i++)
#define ff(i,a,b) for(int i=(a);i>=(b);i--)
#define me(x,y) memset(x,y,sizeof x)
#define pii sd pair<int,int>
#define X first
#define Y second
#define Fr(a) for(auto it:a)
#define dbg(x) sd cout<<#x<<":"<<x<<" "
#define dg(x) sd cout<<#x<<":"<<x<<"\n"
int read(){int w=1,c=0;char ch=getchar();for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;for(;ch>='0'&&ch<='9';ch=getchar()) c=(c<<3)+(c<<1)+ch-48;return w*c;}
void printt(int x){if(x>9) printt(x/10);putchar(x%10+48);}
void print(int x){if(x<0) putchar('-'),printt(-x);else printt(x);}
void put(int x){print(x);putchar('\n');}
void printk(int x){print(x);putchar(' ');}
const int N=1e6+1e4;
int n,m,ans[N],id[N];
char s[N];
struct ACDFA
{
int t[N][30],c,fail[N],fa[N],vis[N];
sd queue<int> q;
sd vector<int> g[N];
void insert(int x)
{
int p=0;
F(i,1,m)
{
if(!t[p][s[i]-'a']) t[p][s[i]-'a']=++c,fa[c]=p;
p=t[p][s[i]-'a'];
ans[p]++;
}
id[x]=p;
}
void add(int u,int c)
{
int p=fail[u];
while(p&&!t[p][c]) p=fail[p];
if(t[p][c]) fail[t[u][c]]=t[p][c];
}
void bfs()
{
F(i,0,25) if(t[0][i]) q.push(t[0][i]);
while(!q.empty())
{
int u=q.front();q.pop();
F(i,0,25) if(t[u][i])
{
add(u,i);
q.push(t[u][i]);
}
}
}
int pd(int u)
{
F(i,0,25) if(t[u][i]) return 0;
return 1;
}
void init()
{
F(i,1,c) g[fail[i]].emplace_back(i);
}
void dfs(int u)
{
for(auto v:g[u])
{
if(vis[v]) continue;
vis[v]=1;
dfs(v);
ans[u]+=ans[v];
}
}
}T;
void solve()
{
n=read();
F(i,1,n)
{
scanf("%s",s+1);
m=strlen(s+1);
T.insert(i);
}
T.bfs();
T.init();
T.dfs(0);
F(i,1,n) put(ans[id[i]]);
}
int main()
{
int T=1;
// T=read();
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号