例题
这道题说的就是把一个长度为(n)的串,以(k)个元素为一组进行分段。问当(k)取何值时能分出最多的不同的段。当然在这道题里面,串是可以反转的,就是说如果两个串镜面对称,那么视为他们是一个串。
不妨先简化一下,先看当串不能反转时如何进行求解。
总体思路
其实这道题的思路很暴力,因为他用的方法就是暴力。先枚举所有的(k(0leqslant kleqslant n)),再进行去重,最后统计能使长串分成最多不同子串的(k)的值。
进一步分析
我们来看一看这种算法的时间复杂度:枚举(k)需要(O(n))的时间复杂度,在每个(k)之中串有$lfloor frac{n}{k} floor $个。所以算法时间复杂度为
将其展开后得到
近似于
提取(n)
可以看出$left( 1+frac{1}{2}+dots +frac{1}{n} ight) $是调和级数,则
得出总时间复杂度为
现在可以看出,解这道题的时间大部分都花在判断和排重上。所以我们就能想到使用哈希。
前缀哈希
由于这道题的处理对象是串,所以我们能想到利用类似前缀和的思路来构造哈希算法。
可以从整数的运算来类比:整数(overline{abcde})可以表示为(a imes 10^4+b imes 10^3+c imes 10^2+d imes 10^1+e imes 10^0)。当我们需要取(overline{bcd})得值时,只需要将(overline{abcde}-e-a imes 10^4)就可以了。
那么如果不是(10)进制,而是(P)进制,那么(P)进制数(overline{abcde})可以表示为(a imes P^4+b imes P^3+c imes P^2+d imes P^1+e imes 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 = 1; k <= n; k++) {
int count = 0;
mp.clear();
// 分段
for (int i = 1; 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;
}