一些定义和核心性质,对于算法理解有很大帮助,不妨考虑字符串全部为1base
- Border:若字符串S的prefix[i]=suffix[i],则称这个前缀或者这个后缀是一个border
- 周期:对于字符串S,存在整数P满足S[i]=S[i−P](p<i≤∣S∣),则P为字符串S的一个周期。这里的周期也常被称为循环覆盖。
- 循环节:若字符串S的周期P满足p∣s.size(),则P是S的一个循环节。
理解:周期用循环覆盖理解,就是长度P的周期拼接若干次以后,S一定作为这其中的子串出现。对于循环节来说,循环节拼接若干次以后,恰好能够得到S。
性质:
- P是S的周期$\Leftrightarrow $$|S|-p$是S的border
- border具有传递性:S的border的border也是S的border.因此得到推论:求S的所有border等价于求递归求所有S前缀的最大Border
- 周期定理:若p, q 均为串S 的周期,则(p, q) 也为S 的周期。
- 一个串的Border 数量是O(N) 个,但他们组成了O(logN) 个等差数列。
KMP板子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| struct KMP { vector<int> nxt; string tt; int len; KMP() {} KMP(string t) { len = t.size(); t = " " + t; tt = t; nxt.resize(len + 1); nxt[1] = nxt[0] = 0; init(tt); }
void init(string t) { for (int i = 2; i <= len; i++) { nxt[i] = nxt[i - 1]; while (nxt[i] && t[i] != t[nxt[i] + 1]) nxt[i] = nxt[nxt[i]]; nxt[i] += (t[i] == t[nxt[i] + 1]); } } vector<int> getnxt() { return nxt; } vector<int> match(string &s, bool oneonly = 0) { int lens = s.size(); s = " " + s; vector<int> stpos; int j = 0; for (int i = 1; i <= lens; i++) { while (j == len || (j && s[i] != tt[j + 1])) j = nxt[j]; if (s[i] == tt[j + 1]) j++; if (j == len) stpos.push_back(i - len + 1); } return stpos; } }
|
https://www.luogu.com.cn/problem/UVA12467
题意:给定一个字符串s,求解最长子串T,满足R(T)是s的前缀。
Sol:考虑将s反转,插入分隔符评接到s后面,我们只需要在反转串范围内求解最长border。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| void solve() { string s;cin >> s; int n = s.size(); string tmp = s; reverse(alls(tmp)); s += "$" + tmp; KMP kmp(s); s = " " + s; int pos = 0;int mx = -1; for (int i = n + 2; i <= n + 2 + n - 1; i++) { if (mx < kmp.nxt[i]) { mx = kmp.nxt[i]; pos = i - mx + 1; } } tmp = s.substr(pos, mx); reverse(alls(tmp)); cout << tmp << endl; }
|
求字符串的最小循环覆盖:设长度为n,答案就是长度为n−nx[n]的前缀
求字符串的最小循环节:检验n能不能被周期整除即可
1.https://www.luogu.com.cn/problem/UVA1328
对于给定字符串 S** 的每个前缀,我们想知道它是否为周期串(周期串定义为由若干最小循环节拼接而成的字符串),若是,输出前缀长度和循环节数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| int cnt = 0; void solve() { int n; cin >> n; string s; cin >> s; KMP kmp(s); cnt++; cout << "Test case #" << cnt << endl; for (int i = 2; i <= n; i++) { deb(i,kmp.nxt[i]); if (kmp.nxt[i]&&i % (i - kmp.nxt[i]) == 0) cout << i << " " << i / (i - kmp.nxt[i]) << endl; } cout << endl; }
|
2.https://www.luogu.com.cn/problem/UVA10298
求一个字符串由多少个重复的子串连接而成
1 2 3 4 5 6 7 8 9 10 11 12
| void solve() { string s; while (cin >> s, s != ".") { KMP kmp(s); int n = s.size(); s = " " + s; if (n % (n - kmp.nxt[n]) == 0)cout<<n/(n-kmp.nxt[n]<<endl; else cout<<n<<endl; } }
|