前缀函数 从KMP到AC自动机

-1.参考资料

OI wiki

0.定义

本文中,字符串下标均从1开始,所以可能和 OI wiki 有一些出入。

定义\(\pi[i]\) (前缀函数)表示 \(s[1\dots i]\) 的最长公共前缀后缀的长度。

形式化的,有:

\[\pi[i]=\max_{k=0\dots i}\{k|s[1\dots k]=s[i-(k-1)\dots i]\} \]

特别的,\(\pi[1]=0\)

1.预处理

首先有一个显然的结论:

\[\pi[i+1]\le \pi[i]+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\}\)

\[s[1\dots j']=s[i-j'+1\dots i]=s[j-j'\dots j] \]

我们发现了

\[s[1\dots j']=s[j-j'\dots j] \]

所以

\[j'=\pi[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;
	}
}
posted @ 2020-08-13 23:26  happydef  阅读(314)  评论(2编辑  收藏  举报