kmp+循环节--学习笔记

kmp入门

求T串在S串中出现了几次之类

先看代码(看一遍就好了,不看也行)

#include <bits/stdc++.h>
using namespace std;
string s,t;
int now,nex[100010]={},ans=0;
int main()
{
    cin>>s>>t;
	now=-1;
	nex[0]=-1;
	for (int i=1;i<t.size();i++)
	{
	     while (now!=-1&&t[now+1]!=t[i]) now=nex[now];
	     if (t[now+1]==t[i]) now++; 
	     nex[i]=now;
	} 
	now=-1;
	for (int i=0;i<s.size();i++)
	{
	    while (now!=-1&&t[now+1]!=s[i]) now=nex[now];
		if (t[now+1]==s[i]) now++;
		if (now==t.size()-1)
		{
		    ans++;
			now=nex[now];
	    }
    }
    cout<<ans;
    return 0;
} 

注:此处字符串默认从0开始,且下文中指得子串都不包括本身,即abc的子串不包括abc
最长的匹配的前缀与后缀也是不包括本身的
暴力么就是枚举T串中每一个位置,然后往后找若干位与S串匹配。
kmp用next数组将这个暴力优化了?next数组是针对T串的;
假设next[i]=j;那么要求T[0…j]与T[i-j,i]这两个子串是相等的;
有些写法中,j表示T串中以i结尾的子串 的最长的前缀与后缀匹配的长度),但是我这里似乎表示的是一个位置(因为从0开始),表示T串中以i结尾的子串 的最长的前缀与后缀匹配的那个前缀的结尾位置,所以长度应是是j+1;
next的具体作用我认为很玄妙(因为不懂…)
首先,我们来想想怎么求next
next[0]=-1;这个很明显吧(字符串第一个字符,且不能是本身,于是就没有,位置就是一个越界的-1)
这里定义一个now初始=-1;
看看上面的代码

	for (int i=1;i<t.size();i++)
	{
	     while (now!=-1&&t[now+1]!=t[i]) now=nex[now];
	     if (t[now+1]==t[i]) now++; 
	     nex[i]=now;
	} 

每次刚进入这个循环时,对应的now值为now=nex[i-1];
这意味着什么呢
***好看啊***
黑线表示T[0…i-1],两条粉线所代表的分别为T[0…now],T[i-1-j…i],根据刚才,now=nex[i-1],又因为给nex的定义所以显然两条粉色线所代表的字符串是一样的。
蓝线代表T串中的第now+1个字符,黄线代表T串中的第i+1个字符;
如果这两条线所代表的字符一样,那么nex[i]=now+1=nex[i-1]+1;
不一样的话,首先你还是要保证第i个字符是要被匹配的,所以就是说我们希望找到一个now,第now+1个字符与第i个字符相等,并且now尽量大,然后nex[i]就可以等于now+1;
然后这时你可以回想一下nex[now]代表了什么。
再来一张图
在这里插入图片描述
请忘掉之前的一切
设黑线为一个串Q,然后两条蓝线分别是Q串的前缀与后缀,设前缀蓝线为串A,后缀为B,且它们就是Q串中最长的匹配的前缀与后缀。因为匹配,所以两个黄圆中的那两个子串也是相同的。
设串A中粉圆的串为Aa,黄圆中的串为Ab;串B中黄圆中的串为Ba
现在再次假设,串Aa与串Ab也是A串中最长的匹配的前缀与后缀,就可以得出,串Aa等于串Ba。所以说,在黑线即串Q中Aa于Ba作为前缀与后缀也是匹配的,只是它们不是最长的,但是由于Aa是A中最长的满足条件的那个串,所以Aa与Ba应该是Q中次长的匹配的前缀与后缀。

然后我们再回到上面,上面已经说到,第now+1个字符与第i个字符不匹配的情况怎么办。
那么now的值肯定要缩减(毕竟匹配不上啊,增加不就完了吗),所以我们应该再再串中找出一个匹配的前缀与后缀,为了长度长一些,这肯定是次长的那个方案,也就是说now=nex[now](now是在前i个字符中最长的匹配,所以它就如上图中的串A,它的最长就是串Aa了)
然后我们再次比对一下第now+1个字符与第i个字符是否匹配,不是就继续now=nex[now];
代码即为

	now=-1;
	nex[0]=-1;
	for (int i=1;i<t.size();i++)
	{
	     while (now!=-1&&t[now+1]!=t[i]) now=nex[now];
	     if (t[now+1]==t[i]) now++; 
	     nex[i]=now;
	} 

当now为-1是就表示没有能匹配的了。
现在你应该能求出nex数组了(心虚)。

对于S串与T串的匹配:
我忍不住又贴代码

	now=-1;
	for (int i=0;i<s.size();i++)
	{
	    while (now!=-1&&t[now+1]!=s[i]) now=nex[now];
		if (t[now+1]==s[i]) now++;
		if (now==t.size()-1)
		{
		    ans++;
			now=nex[now];
	    }
    }

这里也有一个now,这里的now表示S串中以i为结尾的子串最多可以匹配到T串的第now个字符
初值为-1就不解释了吧。
之前S串以i-1结尾时已经可以匹配到第now个字符,同样的如果S串的第i个字符与T串第now+1个字符匹配,那么now就可以++
不匹配的话,同样你也需要保证S串的第i个字符是被匹配的,然后
我们再来一张图
在这里插入图片描述
黑线表示S串,蓝线表示T串,绿线所在为i点,两个粉圆是匹配的,且是最长的。蓝线那的黄圆也是粉圆的nex(总之,就像上面那张图的证明一样,连个黄圆是匹配的,并且长度除粉圆外最长了)。所以如果不匹配,我们可以同样的使now=nex[now](然后now就到了蓝线中黄圆的边上)于是我们再次判断T串的第now+1个字符,是否等于S串第i个字符,否则继now=nex[now],同样如果,now=-1,就表示S串以第i个字符为结尾的子串是不能匹配T串的。
ps:这里说的子串是能包含自身的。。。
我个人认为画图比较好理解(这是原理啊),但是有些人认为例子比较好理解,所以你可以举个例子啥的。

例题。。。我都没写过

kmp求最小循环节

这个循环节的意思是
对于字符串A,我要求它是一个字符串B不断连接所产生的串的子串
例如:
dsadsadsadsadsa
这个字符串是由dsa不断连接而成的吧
那么sadsa就是它所产生的子串了,所以dsa是sadsa的循环节
而你需要求出这个循环节的最小长度是多少。
我依旧需要一张图
在这里插入图片描述
黑线是字符串T,两条蓝线T最长的匹配的前缀与后缀,设前缀为串A,后缀为串B,假设字符串T最后一个字符是第n个字符。
所以T串-B串就是红线所代表的那部分,就叫红线串C吧
所有的粉圆所包含字符串的长度都是C串的长度。
于是粉圆2=粉圆a,粉圆a=粉圆1
所以粉圆1=粉圆2,同理可得相邻的两个粉圆所代表的的字符串都是相等的,所以粉圆都相同
当然粉圆4比较特殊,但是它=粉圆c的啦
显然两个绿园相同,又由于它与粉圆4的特殊关系,所以它一定是所有的粉圆即C串的前缀。
也就是说,如果用串C(红线)当循环节你肯定能够造出一个字符串T。由于nex的极大性,所以串C一定最短。
例题:
[Baltic2009]Radio Transmission–bzoj1355
https://www.lydsy.com/JudgeOnline/problem.php?id=1355
代码

#include <bits/stdc++.h>
using namespace std;
int len,now;
string str;
int nex[1000010];
int main()
{
    cin>>len;
	cin>>str;
	now=-1;
	nex[0]=-1;
	for (int i=1;i<str.size();i++)
	{
	    while (now!=-1&&str[now+1]!=str[i]) now=nex[now];
		if (str[now+1]==str[i]) now++;
		nex[i]=now;
    }
    cout<<len-nex[len-1]-1;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/yu25_21_5/article/details/88604705