KMP
应用:求字符串s在文本T中出现的次数与位置
概念:
后缀 从某个位置 i 开始到整个串末尾结束的一个特殊子串。字符串 S 的从 i 开头的后缀表示为 Suffix(S, i),也就是 Suffix(S, i) = S[i…|S|-1]。
真后缀 除了 S 本身的 S 的后缀
前缀 从串首开始到某个位置 i 结束的一个特殊子串。字符串 S 的以 i 结尾的前缀表示为 Prefix(S, i),也就是 Prefix(S, i) = S[0…i]。
真前缀 除了 S 本身的 S 的前缀
Border 如果一个串s’同时是 S 的真前缀和真后缀,那么称s’是S的一个border
原理:nxt【i】为s【0-i】最长相等前缀和与后缀和.对于nxt【i+1】,最大为nxt【i】+1,此时s【i+1】==s【nxt【i】+1】。那么否则呢,如何找到i+1前一部分与s【】开头一部分最大重合?
现在来看这一组字符串
观察i的前面nxt【i-1】=5,可知A=B,同位代换,D=E.而在nxt【A】=2,即C=D,所以C=E.这就找到了最大重合,又发现s【nxt【A】+1】==s【i】,所以nxt【i】=nxt【A】+1,而A=nxt【i-1】。
就这样,整个函数最难懂的地方就出来了while(j>0&&s[i]!=s[j])j=nxt[j];
上代码
void get_nxt(const char s[]){ int len=strlen(s),j=0;nxt[0]=0; for(int i=1;i<len;++i){ while(j>0&&s[i]!=s[j])j=nxt[j]; if(s[i]==s[j]) ++j; nxt[i]=j; } }
nxt已准备就绪,现在解决T中s的个数
大多数人编了两个函数,但是duck不必,我们灵活运用get_nxt()
为了简便起见,我们用 n 表示字符串 s 的长度,用 m 表示文本 t 的长度。
我们构造一个字符串 s+#+t,其中 # 为一个既不出现在 s 中也不出现在 t 中的分隔符。接下来计算该字符串的前缀函数。现在考虑该前缀函数除去最开始n+1 个值(即属于字符串 s 和分隔符的函数值)后其余函数值的意义。
根据定义, nxt[i] 为右端点在 i 且同时为一个前缀的最长真子串的长度,具体到我们的这种情况下,其值为与 s 的前缀相同且右端点位于 i 的最长子串的长度。
由于分隔符的存在,该长度不可能超过 n。而如果等式 nxt[i]=n 成立,则意味着 s 完整出现在该位置(即其右端点位于位置 i)。注意该位置的下标是对字符串 s+#+t 而言的。
因此如果在某一位置 i 有 π[i]=n 成立,则字符串 s 在字符串 t 的 i-n+1-(n+1)=i-2n 处出现。
上代码
#include<bits/stdc++.h> using namespace std; string s,t,a;int nxt[1010100],n,ans,le; void get_nxt(const string s){ int len=s.size(),j=0;nxt[0]=0; for(int i=1;i<len;++i){ while(j>0&&s[i]!=s[j])j=nxt[j-1]; if(s[i]==s[j]) ++j; nxt[i]=j; if(j==le) ans++; //cout<<j<<endl; } } int main(){ cin>>n; while(n--){ memset(nxt,0,sizeof(nxt)); cin>>s>>t;ans=0; a=s+'#'+t;le=s.size();//cout<<le<<endl; get_nxt(a);cout<<ans<<endl; } }
原创文章,作者:kirin,如若转载,请注明出处:https://blog.ytso.com/276410.html