前缀函数 从KMP到AC自动机

-1.参考资料

OI wiki

0.定义

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

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

形式化的,有:

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

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

1.预处理

首先有一个显然的结论:

[pi[i+1]le pi[i]+1 ]

原因是 (s[1dots pi[i+1]]=s[i-(pi[i+1]-1)dots i]) 可以推出 (s[1dots pi[i+1]-1]=s[i-(pi[i+1]-1)dots i-1])

(k=pi[i+1]-1) 即得 (s[1dots 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[1dots pi[i+1]-1]=s[i-(pi[i+1]-1)dots i-1]) ,因此我们考虑枚举 (s[1dots i-1]) 的公共前缀后缀。

考虑怎么 (O(1)) 找到一个公共前缀后缀的前驱(i.e.仅次于这个公共前缀后缀的第二长度)。

设已知 (s[1dots j]=s[i-j+1dots i]),要求出 (max{j'|s[1dots j']=s[i-j'+1dots i],j'<j})

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

我们发现了

[s[1dots 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;
	}
}
原文地址:https://www.cnblogs.com/happydef/p/kmp.html