例题
这道题说的就是把一个长度为\(n\)的串,以\(k\)个元素为一组进行分段。问当\(k\)取何值时能分出最多的不同的段。当然在这道题里面,串是可以反转的,就是说如果两个串镜面对称,那么视为他们是一个串。
不妨先简化一下,先看当串不能反转时如何进行求解。
总体思路
其实这道题的思路很暴力,因为他用的方法就是暴力。先枚举所有的\(k(0\leqslant k\leqslant n)\),再进行去重,最后统计能使长串分成最多不同子串的\(k\)的值。
进一步分析
我们来看一看这种算法的时间复杂度:枚举\(k\)需要\(O(n)\)的时间复杂度,在每个\(k\)之中串有$\lfloor \frac{n}{k} \rfloor $个。所以算法时间复杂度为
将其展开后得到
近似于
提取\(n\)
可以看出$\left( 1+\frac{1}{2}+\dots +\frac{1}{n} \right) $是调和级数,则
得出总时间复杂度为
现在可以看出,解这道题的时间大部分都花在判断和排重上。所以我们就能想到使用哈希。
前缀哈希
由于这道题的处理对象是串,所以我们能想到利用类似前缀和的思路来构造哈希算法。
可以从整数的运算来类比:整数\(\overline{abcde}\)可以表示为\(a\times 10^4+b\times 10^3+c\times 10^2+d\times 10^1+e\times 10^0\)。当我们需要取\(\overline{bcd}\)得值时,只需要将\(\overline{abcde}-e-a\times 10^4\)就可以了。
那么如果不是\(10\)进制,而是\(P\)进制,那么\(P\)进制数\(\overline{abcde}\)可以表示为\(a\times P^4+b\times P^3+c\times P^2+d\times P^1+e\times P^0\)。(为了优化,在这里定义\(p\)使\(p_i=P^i\))
这跟哈希有什么关系呢?这就是哈希。把以上的推倒总结成哈希公式就是
在这之中,\(h(i)\)表示串\([0,i]\)的哈希值。
易于发现,这样构造哈希值不一会儿就会超出整数的范围,这时候就可以联想到取模。于是就可以把上面的哈希公式优化成
这里的\(m\)需要大质数,以减少哈希碰撞。
会了构造,还要能提取。不难看出,提取公式是
\(h(i,j)\)表示串\([i,j]\)的哈希值。(可以类比十进制)
代码实现
现在来用代码对照上面的思路,辅助理解,我尽量让代码中的变量和代码对应。(不要试图用这段代码去通过原题,它没有考虑逆序的情况)
#include <iostream>
#include <vector>
#include <map>
using namespace std;
const unsigned int P = 19260817; // 进制位
const unsigned int m = 19260817; // 模数
int main() {
int n = 0;
int val[200001] = { 0 };
unsigned long long int p[200001] = { 1 };
unsigned long long int h[200001] = { 1 };
map<unsigned long long int, bool> mp;
int maxn = 0;
vector<int> result;
// 读入
cin >> n;
for (int iter = 1; iter <= n; iter++) {
cin >> val[iter];
}
// 预处理哈希
for (int iter = 1; iter <= n; iter++) {
h[iter] = h[iter - 1] + val[iter] * p[iter - 1];
}
// 枚举k
for (int k = 0; k < n; k++) {
int count = 0;
mp.clear();
// 分段
for (int i = 0; i < n / k; i++) {
int j = i + k; // i和j表示串[i,j]
int H = (h[j]%m - (h[i - 1] * p[j - i + 1])%m)%m; // 计算哈希
// 利用map判重
if (!mp[H]) {
mp[H] = true;
count++;
}
}
// 更新答案
if (maxn == count) {
result.push_back(count);
}
if (maxn < count) {
result.clear();
result.push_back(count);
}
}
// 打印答案
cout << result[0] << result.size() << endl;
for (int iter = 0; iter < result.size(); iter++) {
cout << result[iter] << " ";
}
return 0;
}