• 前缀哈希


    例题

    原题链接

    ​ 这道题说的就是把一个长度为(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;
    }
    
  • 相关阅读:
    量子计算机算法与应用研究论文(转载务必注明出处)
    [置顶] html学习笔记,锚点,超链接,table布局,表头,h,sub,blockquote,ul,li,ol.dl,加入收藏,打印,弹出窗口
    [置顶] 第一天初试linux
    [置顶] Datalist嵌套datalist,页面传值,加密,数据绑定
    [置顶] asp.net(c#)中相对路径(虚拟路径)和物理磁盘路径的转换
    [置顶] c#对于文件的操作
    [置顶] 读取pdf并且在web页面中显示
    [置顶] gridview中嵌套gridview(并实现子gridview的数据绑定),页面传值,加密,数据绑定
    git的使用学习笔记--项目版本操作
    git的使用学习笔记---合并分支
  • 原文地址:https://www.cnblogs.com/Iuppiter/p/12903932.html
Copyright © 2020-2023  润新知