前缀函数 从KMP到AC自动机
-1.参考资料
0.定义
本文中,字符串下标均从1开始,所以可能和 OI wiki 有一些出入。
定义\(\pi[i]\) (前缀函数)表示 \(s[1\dots i]\) 的最长公共前缀后缀的长度。
形式化的,有:
特别的,\(\pi[1]=0\)。
1.预处理
首先有一个显然的结论:
原因是 \(s[1\dots \pi[i+1]]=s[i-(\pi[i+1]-1)\dots i]\) 可以推出 \(s[1\dots \pi[i+1]-1]=s[i-(\pi[i+1]-1)\dots i-1]\)
令 \(k=\pi[i+1]-1\) 即得 \(s[1\dots k]=s[i-1-(k-1)\dots i-1]\)
根据 \(\pi[i]\) 的定义即得 \(\pi[i]\ge k=pi[i+1]-1\),得证。
于是考虑暴力枚举预处理,然后利用字符串哈希来判别字符串是否相等。
复杂度 \(O(\sum_{i=1}^{n-1}\pi[i]+1-\pi[i]-1)=O(n)\)。
但是这个算法不一定是正确的,可能会被卡模数,我们考虑继续优化。
首先根据上面那个结论我们可以得到一个推论,就是 \(s[1\dots \pi[i+1]-1]=s[i-(\pi[i+1]-1)\dots i-1]\) ,因此我们考虑枚举 \(s[1\dots i-1]\) 的公共前缀后缀。
考虑怎么 \(O(1)\) 找到一个公共前缀后缀的前驱(i.e.仅次于这个公共前缀后缀的第二长度)。
设已知 \(s[1\dots j]=s[i-j+1\dots i]\),要求出 \(\max\{j'|s[1\dots j']=s[i-j'+1\dots i],j'<j\}\) 。
有
我们发现了
所以
Q.E.D.
转移的次数仍然是 \(O(n)\) 的,但是因为我们避免了字符串比较,所以总复杂度 \(O(n)\) 。
代码:
void init(char*s,int n,int*nxt){
nxt[1]=0;
for(int i=2,j;i<=n;i++){
while(j&&s[j+1]!=s[i])j=nxt[j];
if(s[i]==s[j+1])j++;
nxt[i]=j;
}
}