题目描述
难度分:2000
输入长度≤105的字符串s,只包含大写英文字母。
对于每个既是s前缀又是s后缀的子串t,计算t的长度,以及t在s中的出现次数。
注意t可以等于s。
输出格式:
- 第一行,输出有多少种长度不同的t。把这个数字记作k。
- 然后输出k行,每行两个数,分别表示t的长度和t在s中的出现次数。
必须按照t长度递增的顺序输出这k行。
输入样例1
ABACABA
输出样例1
3
1 4
3 2
7 1
输入样例2
AAA
输出样例2
3
1 3
2 2
3 1
算法
Z函数+后缀和
设n为s串的长度,先利用扩展KMP算法求出z数组,其中的元素z[i]就表示后缀s[n−i…n−1]的所有前缀中,也是整个s串的前缀的最大长度,本质上cnt数组就是s串一堆前缀的频数。然后开辟一个cnt数组来存储z[i]的频数,其中i∈[0,n)。
因为s长度为i的前缀是长度为i+1、i+2、……、n的前缀的前缀,此时这么来理解,前缀s[0…i−1]都作为s中某些后缀的前缀在后面出现,所以Σnlen=icnt[len]就是s串长度为i的前缀在s中作为子串一共出现了多少次。因此预处理出cnt的后缀和(直接原数组cnt上操作即可),遍历i∈[1,n],只要满足i=z[n−i]就说明s长度为i的前缀等于相同长度的后缀,将(i,cnt[i])存入答案数组res。
最后先输出res数组的大小,然后按照第一关键字递增的顺序输出res数组的二元组信息就可以了。
复杂度分析
时间复杂度
Z函数的时间复杂度为O(n);求cnt数组的时间复杂度为O(n);求cnt数组的后缀和时间复杂度也是O(n);最后输出结果数组的时间复杂度仍然是O(n)。因此,整个算法的时间复杂度为O(n)。
空间复杂度
除了输入的字符串,z数组、cnt数组,以及结果数组res都是O(n)的空间消耗,这也是整个算法的额外空间复杂度。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
template<class S>
vector<int> z_algo(const S& s) {
int n = s.size();
vector<int> r(n + 1);
r[0] = 0;
for(int i = 1, j = 0; i <= n; i++) {
int& k = r[i];
k = j + r[j] <= i? 0: min(j + r[j] - i, r[i - j]);
while(i + k < n and s[k] == s[i + k]) k++;
if(j + r[j] < i + r[i]) j = i;
}
r[0] = n;
return r;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(NULL); cout.tie(NULL);
string s;
cin >> s;
int n = s.size();
vector<int> cnt(n + 2);
vector<int> z = z_algo(s);
for(int i = 0; i < n; i++) {
cnt[z[i]]++;
}
vector<pair<int, int>> res;
for(int i = n; i >= 1; i--) {
cnt[i] += cnt[i + 1];
if(z[n - i] == i) {
res.push_back({i, cnt[i]});
}
}
int k = res.size();
cout << k << '\n';
for(int i = k - 1; i >= 0; i--) {
cout << res[i].first << ' ' << res[i].second << '\n';
}
return 0;
}