一、题意
每组数据第一行一个n,第二行给一个长度为n的字符串。
要求列出所有的(i, k)满足该字符串的前i个字符组成的前缀子串是由k个子串循环而成的。
二、解析
中心问题就是求出某个字符串的循环节。
求循环节应该马上想到KMP算法。
求出Next数组后,根据Next数组的定义,Next[i]=j表示j为s[i]匹配失败后需要跳回的位置,也即是i和j之前可能为相同的串。对于一个循环串来说,它的Next数组“跳回”的长度正好就是一个循环节。因此通过i-Next[i]即可计算出s[1..i)的循环节长度,但注意可能字符串不是循环字符串,因此还要判定字符串长度i是否能被循环节长度i-Next[i]整除。
三、代码
#include <iostream> #include <algorithm> #include <string> using namespace std; const int maxn = 1000000 + 5; int n, Next[maxn], kase = 1; string str; void getNext(string& p, int m) { Next[0] = -1; int i = 0, j = -1; while(i < m) { if(j == -1 || p[i] == p[j]) i ++, j ++, Next[i] = j; else j = Next[j]; } } int main() { while(cin >> n && n) { cout << "Test case #" << kase ++ << "\n"; cin >> str; getNext(str, n); for(int i = 2; i <= n; i ++) { if(Next[i] && i % (i - Next[i]) == 0) cout << i << " " << i / (i - Next[i]) << "\n"; } cout << endl; } }
四、归纳
- KMP算法要牢记!!虽然这里只用了getNext数组的求法,但完整的算法也应该记住。
int KMP(const string& t, int n, const string& p, int m, int* Next) { // 直接返回第一次匹配的写法 int i = 0, j = 0; while(i < n && j < m) { if(j == -1 || t[i] == p[j]) i ++, j ++; else j = Next[j]; } if(j == m) return i - j; return -1; } int KMP(const string& t, int n, const string& p, int m, int* Next) { // 返回匹配次数的写法 int i = 0, j = 0; while(i < n) { if(j == -1 || t[i] == p[j]) i ++, j ++; else j = Next[j]; if(j == m) cnt ++, j = Next[j]; } return cnt; } void getNext(string& p, int m) { Next[0] = -1; int i = 0, j = -1; while(i < m) { if(j == -1 || p[i] == p[j]) i ++, j ++, Next[i] = j; else j = Next[j]; } }
- 要注意KMP算法与循环节的关系
- 要理解清楚Next数组的含义:Next数组是针对匹配串(短串)的;比如abac,Next[3]=1,表示c匹配失败后会跳到b的位置进行下一次匹配,这样就节省了重复匹配a的时间。
加油!只剩半个月不到了