前缀哈希

例题

原题链接

​ 这道题说的就是把一个长度为(n)的串,以(k)个元素为一组进行分段。问当(k)取何值时能分出最多的不同的段。当然在这道题里面,串是可以反转的,就是说如果两个串镜面对称,那么视为他们是一个串。

​ 不妨先简化一下,先看当串不能反转时如何进行求解。

总体思路

​ 其实这道题的思路很暴力,因为他用的方法就是暴力。先枚举所有的(k(0leqslant kleqslant n)),再进行去重,最后统计能使长串分成最多不同子串的(k)的值。

进一步分析

​ 我们来看一看这种算法的时间复杂度:枚举(k)需要(O(n))的时间复杂度,在每个(k)之中串有$lfloor frac{n}{k} floor $个。所以算法时间复杂度为

[sum_{k=1}^n{lfloor frac{n}{k} floor} ]

将其展开后得到

[lfloor frac{n}{1} floor +lfloor frac{n}{2} floor +dots +lfloor frac{n}{n} floor ]

近似于

[frac{n}{1}+frac{n}{2}+dots +frac{n}{n} ]

提取(n)

[nleft( 1+frac{1}{2}+dots +frac{1}{n} ight) ]

可以看出$left( 1+frac{1}{2}+dots +frac{1}{n} ight) $是调和级数,则

[1+frac{1}{2}+dots +frac{1}{n}approx ln x<log _2n ]

得出总时间复杂度为

[Oleft( nlog 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)

​ 这跟哈希有什么关系呢?这就是哈希。把以上的推倒总结成哈希公式就是

[left{ egin{array}{l} hleft( i ight) =hleft( i-1 ight) +valleft( i ight) cdot p_{i-1}\ hleft( 1 ight) =valleft( 1 ight)\ end{array} ight. ]

在这之中,(h(i))表示串([0,i])的哈希值。

​ 易于发现,这样构造哈希值不一会儿就会超出整数的范围,这时候就可以联想到取模。于是就可以把上面的哈希公式优化成

[left{ egin{array}{l} hleft( i ight) =left( hleft( i-1 ight) mod m+left( valleft( i ight) cdot p_{i-1} ight) mod m ight) mod m\ hleft( 1 ight) =valleft( 1 ight) mod m\end{array} ight. ]

这里的(m)需要大质数,以减少哈希碰撞。

​ 会了构造,还要能提取。不难看出,提取公式是

[hleft( i,j ight) =hleft( j ight) -hleft( i-1 ight) cdot p_{j-i+1} ]

(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;
}
原文地址:https://www.cnblogs.com/Iuppiter/p/12903932.html