前缀哈希

例题

原题链接

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

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

总体思路

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

进一步分析

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

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

将其展开后得到

\[\lfloor \frac{n}{1} \rfloor +\lfloor \frac{n}{2} \rfloor +\dots +\lfloor \frac{n}{n} \rfloor \]

近似于

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

提取\(n\)

\[n\left( 1+\frac{1}{2}+\dots +\frac{1}{n} \right) \]

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

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

得出总时间复杂度为

\[O\left( n\log 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\)

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

\[\left\{ \begin{array}{l} h\left( i \right) =h\left( i-1 \right) +val\left( i \right) \cdot p_{i-1}\\ h\left( 1 \right) =val\left( 1 \right)\\\end{array} \right. \]

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

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

\[\left\{ \begin{array}{l} h\left( i \right) =\left( h\left( i-1 \right) \%m+\left( val\left( i \right) \cdot p_{i-1} \right) \%m \right) \%m\\ h\left( 1 \right) =val\left( 1 \right) \%m\\\end{array} \right. \]

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

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

\[h\left( i,j \right) =h\left( j \right) -h\left( i-1 \right) \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 = 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;
}

猜你喜欢

转载自www.cnblogs.com/Iuppiter/p/12903932.html
今日推荐