对于kmp的理解:https://blog.csdn.net/v_july_v/article/details/7041827
next数组模板:
void GetNext(char* p,int next[])
{
int pLen = strlen(p);
next[0] = -1;
int k = -1;
int j = 0;
while (j < pLen - 1)
{
//p[k]表示前缀,p[j]表示后缀
if (k == -1 || p[j] == p[k])
{
++k;
++j;
next[j] = k;
}
else
{
k = next[k];
}
}
}
优化后的next数组模板:
void GetNextval(char* p, int next[])
{
int pLen = strlen(p);
next[0] = -1;
int k = -1;
int j = 0;
while (j < pLen - 1)
{
//p[k]表示前缀,p[j]表示后缀
if (k == -1 || p[j] == p[k])
{
++j;
++k;
//较之前next数组求法,改动在下面4行
if (p[j] != p[k])
next[j] = k; //之前只有这一行
else
//因为不能出现p[j] = p[ next[j ]],所以当出现时需要继续递归,k = next[k] = next[next[k]]
next[j] = next[k];
}
else
{
k = next[k];
}
}
}
kmp模板:
int KmpSearch(char* s, char* p)
{
int i = 0;
int j = 0;
int sLen = strlen(s);
int pLen = strlen(p);
while (i < sLen && j < pLen)
{
//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
if (j == -1 || s[i] == p[j])
{
i++;
j++;
}
else
{
//②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
//next[j]即为j所对应的next值
j = next[j];
}
}
if (j == pLen)
return i - j;
else
return -1;
}
KMP最小循环节、循环周期:
定理:假设S的长度为len,则S存在最小循环节,循环节的长度L为len-next[len],子串为S[0…len-next[len]-1]。
(1)如果len可以被len - next[len]整除,则表明字符串S可以完全由循环节循环组成,循环周期T=len/L。
(2)如果不能,说明还需要再添加几个字母才能补全。需要补的个数是循环个数L-len%L=L-(len-L)%L=L-next[len]%L,L=len-next[len]。
注意:这里的next数组模板要改成while( j < plen)因为要用到整个字符串的next值。
题型:
一.裸题:
NumberSequence
Given two sequences of numbers : a[1], a[2], ...... ,a[N], and b[1], b[2], ...... , b[M] (1 <= M <= 10000, 1 <= N <=1000000). Your task is to find a number K which make a[K] = b[1], a[K + 1] =b[2], ...... , a[K + M - 1] = b[M]. If there are more than one K exist, outputthe smallest one.
Input
The first line of input is a number T which indicate thenumber of cases. Each case contains three lines. The first line is two numbersN and M (1 <= M <= 10000, 1 <= N <= 1000000). The second linecontains N integers which indicate a[1], a[2], ...... , a[N]. The third linecontains M integers which indicate b[1], b[2], ...... , b[M]. All integers arein the range of [-1000000, 1000000].
Output
For each test case, you should output one line which onlycontain K described above. If no such K exists, output -1 instead.
Sample Input
2
13 5
1 2 1 2 3 1 2 3 1 3 2 1 2
1 2 3 1 3
13 5
1 2 1 2 3 1 2 3 1 3 2 1 2
1 2 3 2 1
Sample Output
6
-1
题意:寻找字串的位置并输出,若没有就输出-1
代码:
#include
#include
#include
using namespace std;
int nex[10005];
int s[1000000];
int p[10005];
int m,n;
void GetNextval()
{
int pLen = m;
nex[0] = -1;
int k = -1;
int j = 0;
while (j < pLen - 1)
{
if (k == -1 || p[j] == p[k])
{
++j;
++k;
if (p[j] != p[k])
nex[j] = k;
else
nex[j] = nex[k];
}
else
{
k = nex[k];
}
}
}
int KmpSearch()
{
int i = 0;
int j = 0;
int sLen = n;
int pLen = m;
while (i < sLen && j < pLen)
{
if (j == -1 || s[i] == p[j])
{
i++;
j++;
}
else
{
j = nex[j];
}
}
if (j == pLen)
return i - j+1;
else
return -1;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i = 0 ; i < n ; i ++)
scanf("%d",&s[i]);
for(int i = 0 ; i < m ; i ++)
scanf("%d",&p[i]);
GetNextval();
printf("%d\n",KmpSearch());
}
return 0;
}
二:变形题:寻找字串的个数(可重复)
Oulipo
3 BAPC BAPC AZA AZAZAZA VERDI AVERDXIVYERDIANSample Output
1 3 0
题意:每组输入两个字符串,判断第一个字符串在第二个字符串中出现了几次,这个题里的next数组要计算最后一个,即while(j < plen),可以对比下一道题理解一下。
代码:
#include
#include
#include
#include
#include
#include
#include
#include
三:变形题:寻找子串的个数(一直往下走不可回溯)
abcde a3 aaaaaa aa #Sample Output
0 3
这个题的next数组不需要计算出最后一个,即while(j < plen - 1)即可。
代码:
#include
#include
#include
#include
#include
#include
#include
#include
四:变形题:字符串周期问题
Cyclic Nacklace
3 aaa abca abcdeSample Output
0 2 5
题意:每个字母代表一种珍珠,要求补最少的珠子使珍珠串成为循环串
思路:转化为求最小循环字串即可,套用上面的公式:
KMP最小循环节、循环周期
定理:假设S的长度为len,则S存在最小循环节,循环节的长度L为len-next[len],子串为S[0…len-next[len]-1]。
(1)如果len可以被len - next[len]整除,则表明字符串S可以完全由循环节循环组成,循环周期T=len/L。
(2)如果不能,说明还需要再添加几个字母才能补全。需要补的个数是循环个数L-len%L=L-(len-L)%L=L-next[len]%L,L=len-next[len]。
注意:这里的next数组模板要改成while( j < plen)因为要用到整个字符串的next值。
代码:
#include
#include
#include
using namespace std;
char partten[100000+10];
int nex[100000+10];
int pLen;
void GetNext()
{
nex[0] = -1;
int k = -1;
int j = 0;
while (j < pLen )
{
//p[k]表示前缀,p[j]表示后缀
if (k == -1 || partten[j] == partten[k])
{
++k;
++j;
nex[j] = k;
}
else
{
k = nex[k];
}
}
}
int main()
{
int t;
cin >> t;
while(t--)
{
scanf("%s",partten);
pLen = strlen(partten);
GetNext();
int circle_len = pLen - nex[pLen];//代表循环节的长度
if(circle_len != pLen && pLen%circle_len==0)//如果可以多次循环
printf("0\n");
else
printf("%d\n",circle_len - nex[pLen]%circle_len);//取余的作用:abcab,去掉abc
} //循环节的长度减去已经匹配的长度
return 0;
}
3 aaa 12 aabaabaabaab 0Sample Output
Test case #1 2 2 3 3 Test case #2 2 2 6 2 9 3 12 4
题意:给出一个字符串,找出由循环子字符串前缀,输出前缀长度及其中相同的子字符串数(即此前缀中循环子串的个数)
思路:这个相当于遍历每一个字母时都判断一下有没有循环字串,可以在求next数组的时候一起完成
代码:
#include
#include
#include
using namespace std;
char p[1000000+10];
int nex[1000000+10];
void Getnext()
{
int plen = strlen(p),circle_len;
int k = -1 ;
int j = 0 ;//现在的字符 -
nex[0] = -1;
while( j < plen)
{
if(k == -1 || p[k]==p[j])
{
++j;
++k;
nex[j] = k;
circle_len = j - nex[j];//nex[j]记录的时上一个字符的最大公共前后缀,而j恰好是正常顺序中的次序
if(nex[j] > 0 && j % circle_len == 0)//nex[j]>0代表有前后缀,并且前后缀循环
printf("%d %d\n",j,j/circle_len);
}
else
k = nex[k];
}
}
int main()
{
int t,i = 1;
while(scanf("%d",&t)!=EOF && t)
{
scanf("%s",p);
printf("Test case #%d\n",i++);
Getnext();
printf("\n");
}
return 0;
}
bcabcab efgabcdefgabcdeSample Output
3 7
题目大意:
有一个字符串A,假设A是“abcdefg”, 由A可以重复组成无线长度的AAAAAAA,即“abcdefgabcdefgabcdefg.....”.
从其中截取一段“abcdefgabcdefgabcdefgabcdefg”,取红色部分为截取部分,设它为字符串B。
现在先给出字符串B, 求A最短的长度。
分析与总结:
设字符串C = AAAAAAAA.... 由于C是由无数个A组成的,所以里面有无数个循环的A, 那么从C中的任意一个起点开始,也都可以有一个循环,且这个循环长度和原来的A一样。(就像一个圆圈,从任意一点开始走都能走回原点)。
所以,把字符串B就看成是B[0]为起点的一个字符串,原问题可以转换为:求字符串B的最小循环节
根据最小循环节点的求法,很容易就可以求出这题。
代码:
#include
#include
#include
using namespace std;
char s[1000000+10];
int nex[1000000+10];
void getnext()
{
int plen = strlen(s);
int k = -1;
int j = 0;
nex[0] = -1;
while( j < plen)
{
if(k == -1 || s[j]==s[k])
{
j++;
k++;
nex[j] = k;
}
else
k = nex[k];
}
printf("%d\n",plen - nex[plen]);
}
int main()
{
while(scanf("%s",s)!=EOF)
getnext();
return 0;
}
abcd aaaa ababab .Sample Output
1 4 3Hint
题意:这个就比较裸的求循环字串的长度了,直接套就行了,就是注意如果没有的话要输出1
代码:
#include
#include
#include
#include
using namespace std;
char p[100000000+5];
int nex[100000000+5];
void getnext()
{
int plen = strlen(p);
int k = -1 ;
int j = 0;
nex[0] = -1;
while( j < plen)
{
if(k == -1 || p[k]== p[j])
{
k++;
j++;
nex[j] = k;
}
else
k = nex[k];
}
}
int main()
{
while(scanf("%s",p)&& p[0] != '.')
{
int plen = strlen(p);
getnext();
int circle = plen - nex[plen];
if(circle != plen &&plen%circle==0)
printf("%d\n",plen/circle);
else
printf("1\n");
}
return 0;
}
五:对next数组的理解
Seek the Name, Seek the Fame
ababcababababcabab aaaaaSample Output
2 4 9 18 1 2 3 4 5
题意:求所有匹配字符串前缀后缀的长度
思路:其实next数组就表示的时最长的前缀和后缀匹配,那么只要next数组的值不为零的话,就代表有前后缀匹配,一直递归下去,注意整个字符串也符合条件。所以求出最终的next数组一直递归就可以了。
代码:
#include
#include
#include
using namespace std;
char p[400000+10];
int nex[400000+10];
int plen ;
void getnext()
{
plen = strlen(p);
int k = -1;
int j = 0;
nex[0] = -1;
while( j < plen )
{
if( k == -1 || p[j] == p[k])
{
j++;
k++;
nex[j] = k;
}
else
k = nex[k];
}
}
int main()
{
int s[40000] ,i = 0;
while(scanf("%s",p)!=EOF)
{
i = 0;
getnext();
int k = plen;//整个字符串的长度
while(nex[k] != -1)
{
s[i++] = k;//递归索引求出所有的前后缀长度
k = nex[k];
}
for(int j = --i; j >= 0; j--)//倒序输出即可
printf("%d ",s[j]);
printf("\n");
}
return 0;
}
1 4 ababSample Output
6
题意:求串的前缀在串中出现的次数
思路:KMP的next[]数组的应用,处理完next[]数组后,则以第i个字母为结尾的串中出现前缀的个数就是本身加上dp[next[i]]的结果,因为我们知道next[i]数组代表的是 和前缀匹配的长度,所以可以归纳到前缀中
代码:
#include
#include
#include
using namespace std;
char s[200005];
int nex[200005];
int dp[200005];
void getnext()
{
int slen = strlen(s);
int k = -1;
int j = 0;
nex[0] = -1;
while(j < slen)
{
if(k == -1 || s[k]==s[j])
nex[++j] = ++k;
else
k=nex[k];
}
}
int main()
{
int t,n;
cin >> t;
while(t--)
{
cin >> n;
scanf("%s",s);
getnext();
int slen = strlen(s);
memset(dp,0,sizeof(dp));
int sum = 0 ;
for(int i = 1 ; i <=slen; i++)
{
dp[i] = dp[nex[i]]+1;
sum =(sum+dp[i])%10007;
}
printf("%d\n",sum);
}
}
六:求多个字符串的最长公共字串
Blue Jeans
3 2 GATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 3 GATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATACCAGATA GATACTAGATACTAGATACTAGATACTAAAGGAAAGGGAAAAGGGGAAAAAGGGGGAAAA GATACCAGATACCAGATACCAGATACCAAAGGAAAGGGAAAAGGGGAAAAAGGGGGAAAA 3 CATCATCATCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC ACATCATCATAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AACATCATCATTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSample Output
no significant commonalities AGATAC CATCATCAT
题意:给你几个DNA序列长度为60,以第一个为模板,找到之后的DNA中与模板DNA相同的子序列,且保证子序列最长(长度大于等于3)。
思路: 暴力寻找,枚举相同序列的长度以第一个DNA为模板向其他串中找。其中有个技巧性的地方就是strstr()函数的使用,strstr(a,b)函数为在a中找b,如果可以找到b那么会返回最初始找到b时的位置的地址,若找不到b则返回NULL。
代码:
#include
#include
#include
using namespace std;
char tem[65];
char str[15][65];
char fin[65];
int m;
int judge()
{
int i;
for(i = 1 ;i < m; i++)
{
if(strstr(str[i],tem)==0)//在str[i]中是否存在tem,存在就返回所在位置 否则返回null
return 0;
}
return 1;
}
int main()
{
int n,i,k,j;
cin >> n;
while(n--)
{
cin >> m;
for(i = 0; i < m; i++)
cin >> str[i];
k = 3;
int flag = 0;
while(k <= 60)//暴力搜索 字符串长度遍历
{
for(i = 0 ; i <= 60 - k;i++)//枚举字符串长度
{
memset(tem,'\0',sizeof(tem));
for( j = 0 ; j < k ; j++)
tem[j] = str[0][i+j];//i记录字符串的起始位置
tem[k] = '\0';
if(judge())
{
flag = 1;
strcpy(fin,tem);//找到了就记录下来
}
}
k++;
}
if(flag==1)
cout<
七:求a串前缀和b串后缀最长公共字串
Simpsons’ Hidden Talents
clinton homer riemann marjorieSample Output
0 rie 3
代码:
#include
#include
#include
using namespace std;
char s1[100005];
char s2[50005];
int nex[100005];
int len1,len2,len;
void getnext()
{
int k = -1;
int j = 0;
nex[0] = -1;
while(jlen1||j>len2)
j = nex[j];
if(j == 0)
printf("0\n");
else
{
for(i = 0 ; i < j ; i++)
printf("%c",s1[i]);
printf(" %d\n",j);
}
}
return 0;
}