next数组的性质:
性质:如果len%(len-next[len])==0,则字符串中必存在最小循环节,且循环次数即为len/(len-next[len]);
证明:在前len个字符组成的字符串,存在最小循环节k,那么next[len]=len-k;(为什么呐?因为next数组的定义就是最大前后缀相同的子串的长度,len的总长度减去最小循环节,比如有3个循环节,减去一个剩下两个,就是最大循环节)那么循环次数就是len/(len-next[len]);因为len-next[len]=k;所以得出公式;
(此段引用自:https://www.cnblogs.com/wuwangchuxin0924/p/5977503.html)
hdu2087
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2087
题意:求str1中有多少个str2.
题解:
这题数据很水,不用kmp也能过。(其实是因为之前kmp写错了)
用kmp的话,就是一般kmp的做法在str2匹配完后就他跳出循环,这时候把跳出循环改成str2从头继续匹配即可。
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 2005;
#define eps 1e-8
#define INIT(x) memset(x,0,sizeof(x))
typedef long long ll;
string s2,s1;
int nxt[maxn];
void get_next(string s)
{
nxt[0] = -1;
nxt[1] = 0;
int cn = 0;
int i = 2;
while(i<s.length())
{
if(s[i-1]==s[cn])
nxt[i++] = ++cn;
else if(cn>0)
cn = nxt[cn];
else
nxt[i++] = 0;
}
}
int kmp()
{
INIT(nxt);
int ans = 0;
get_next(s2);
int i=0,j=0;
while(i<s1.length()&&j<s2.length())
{
if(s1[i]==s2[j]) {
i++,j++;
}else if(nxt[j]==-1) {
i++;
}else {
j = nxt[j];
}
if(j==s2.length()) {
ans++;
j = 0;
}
}
return ans;
}
int main()
{
while(cin>>s1)
{
if(s1[0]=='#') break;
cin>>s2;
int ans = kmp();
cout<<ans<<endl;
}
return 0;
}
poj2752
题目链接:http://poj.org/problem?id=2752
题意:
给一个字符串s,要求s中寻找子串,使得子串既是原串的前缀也是原串的后缀。求满足条件的子串,并其次输出它们。
题解:
根据题意,至少有一个子串,就是s本身。
其余的根据next数组的性质,求的就是公共的前后缀,所以利用next数组的性质可以比较轻松的求出来。
举个例子,abababab,在位置9(从0开始)的next数组值为6,那么6就是题目的一个解;
在位置6的值为4,那么4也是其的一个解。这是因为开头的值不会变,而最后位置的值和next数组对应位置的值必然相等得到的。
所以,结果就是next[m],next[next[m]]这样递推下去。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <map>
#include <vector>
#include <set>
#include <algorithm>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 400005;
#define eps 1e-8
#define INIT(x) memset(x,0,sizeof(x))
typedef long long ll;
string s;
int nxt[maxn];
vector<int> e;
void get_next(string s)
{
nxt[0] = -1;
nxt[1] = 0;
int i = 2;
int cn = 0;
while(i<=s.length())
{
if(s[i-1]==s[cn])
nxt[i++] = ++cn;
else if(cn>0)
cn = nxt[cn];
else
nxt[i++] = 0;
}
}
int main()
{
while(cin>>s)
{
INIT(nxt);
e.clear();
get_next(s);
int m = s.length();
while(m)// m必然大于等于0,因为它相当于字符串s2的长度
{
e.push_back(m);
m = nxt[m];
}
for(int i=e.size()-1;i>0;i--)
cout<<e[i]<<" ";
cout<<e[0]<<endl;
}
return 0;
}
poj2406 最小循环节
题目链接:http://poj.org/problem?id=2406
题意:
给一个字符串s,求一个字符串a,使得s是由a重复k次构成的,令k尽量大,求出这个a。
题解:
这题就是最小循环节问题。
首先我们来看ababab这个字符串,next[6] = 4, 所以5,6位置必然和3,4位置相等,根据next数组的递推原则,next[4] = 2,所以3,4位置和1,2位置相等,因此我们可以推出。如果next[m] = x, m%(m-x)==0, 那么最有解一定就是m-x开始的那部分字符串。
可以看开头的next数组的性质。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <map>
#include <vector>
#include <set>
#include <algorithm>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 1000005;
#define eps 1e-8
#define INIT(x) memset(x,0,sizeof(x))
typedef long long ll;
string s;
int nxt[maxn];
vector<int> e;
void get_next(string s)
{
nxt[0] = -1;
nxt[1] = 0;
int i = 2;
int cn = 0;
while(i<=s.length())
{
if(s[i-1]==s[cn])
nxt[i++] = ++cn;
else if(cn>0)
cn = nxt[cn];
else
nxt[i++] = 0;
}
}
int main()
{
while(cin>>s)
{
if(s[0]=='.')
break;
INIT(nxt);
get_next(s);
int m = s.length();
int x = nxt[m];
int ans = 0;
while(~x)
{
if(m%(m-x)==0) {
ans = m-x;
break;
}
x = nxt[x];
}
cout<<m/ans<<endl;
}
return 0;
}