UVA1328题解
前情提要
本题解重在使大家理解。
本题需要 KMP,相信阅读本篇的大佬都会吧。
没学过也没关系,点这里。这是一篇我喜欢的讲解,不喜勿喷。
分析
看见本题的第一感就是会与 KMP 中的 \(next\) 数组有关。
我们通过下面证明可以得出:满足 \(i \bmod len = 0\),且 \(S[1 \sim i-len] = S[len+1 \sim i]\) 这样的 \(len\) 为循环节,其中 \(i\) 为 \(S\) 字符串中某个前缀的末尾项。
证必要性:
不妨我们先设 \(len\) 为字符串 \(S[1 \sim i]\) 循环节的长度,我们明显可得:\(i \bmod len = 0\),且 \(S[1 \sim i-len]=S[len+1 \sim i]\)。如:
\(S= \texttt{abcabc}\)。则 \(len=3\),\(i=6\)。我们可以发现 \(i \bmod len=0\)。\(S[1 \sim i-len]=\texttt{abc}\)。\(S[len+1 \sim i]=\texttt{abc}\)。所以 \(S[1 \sim i-len]=S[len+1 \sim i]\)。
对于 \(S[1 \sim i-len]=S[len+1 \sim i]\) 其实我们也可以这样理解:因为 \(S[1 \sim i]\) 是由 \(k\) 个长度为 \(len\) 的循环元组成,\(S[1 \sim i-len]\) 与 \(S[len+1 \sim i]\) 均是由 \(k - 1\) 个长度为 \(len\) 的循环元组成,那么自然可以得出 \(S[1 \sim i-len] = S[len+1 \sim i]\)。
至于 \(i \bmod len = 0\),我相信不用多讲了吧。
证充分性:
这里,我们就可以设 \(len\) 能整除 \(i\) 且 \(S[1 \sim i-len] = S[len+1 \sim i]\)。自然,我们可以得出以上两个字符串的长度能整除 \(len\)。
得出了这个结论我们又可以开始推导:\(S[1 \sim len] = S[len + 1 \sim 2 \times len]\),\(S[len+1 \sim 2 \times len] = S[2 \times len+1 \sim 3 \times len]\),依次类推,我们可以得出结论 \(S[1 \sim i - len]\) 与 \(S[len +1 \sim i]\) 均是由 \(k\) 个长度为 \(len\),也就是 \(S[1 \sim len]\) 是 \(S[1 \sim i]\) 的循环节。
还是看不懂?
没事看一下图:
注:图中 \(len=2,i=8\)。
解释一下。本图中:
\(S[1 \sim len]= \texttt{ab}\)。
\(S[len + 1 \sim 2 \times len]= \texttt{ab}\)。
\(S[2 \times len+1 \sim 3 \times len]= \texttt{ab}\)。
好,回到现在,我们已经证了满足能整除 \(i\) 且 \(S[1 \sim i-len]=S[len+1 \sim i]\) 的 \(len\) 一定是循环结,那这又和 \(next\) 数组有什么关系呢?
你们看,这 \(S[1 \sim i-len]=S[len+1 \sim i]\) 不就是 \(S[1 \sim next_i] = S[i-next_i+1 \sim i]\) 吗?
也就是 \(i-len=next_i\)。
我们要求长度最小的循环节,就需要 \(i-len\) 越大!这不就巧了?\(next_i\) 刚好符合这样的条件。
那么这道题也就解出来了!
回顾过程
- 求 \(next\) 数组。
- 判断 \(i-next\) 是否能整除 \(i\)。
- 若步骤 2 成立,那么 \(len \gets i-next_i\),否则没有循环节。
- 重复 2,3。
代码
本人马蜂不好,不喜勿喷。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 100;
string s;
int n, nxt[N], num;//其中的nxt也就是上文的next数组。
int main() {
ios::sync_with_stdio(0);
while (cin >> n && n) {//重复输入
cin >> s; s = " " + s;//我习惯从1开始遍历
nxt[1] = 0;
for (int i = 2, j = 0; i <= n; i++) {
while (j > 0 && s[i] != s[j + 1]) j = nxt[j];
if (s[i] == s[j + 1]) j++;
nxt[i] = j;
}//步骤1:求next数组
cout << "Test case #" << ++num << "\n";
for (int i = 2; i <= n; i++) {
if (i % (i - nxt[i]) == 0 && i / (i - nxt[i]) > 1)//步骤2
cout << i << " " << i / (i - nxt[i]) << endl;//步骤3
}
cout << endl;
}
return 0;
}
The End
部分内容来自 lyd 的算法竞赛进阶指南。图片自绘。