数据结构基础【字符串Hash】

字符串Hash

        取一固定值P,把字符串看作P进制数,并分配一个大于0的数值,代表每种字符。一般般来说,我们分配的数值都远小于P。例如,对于小写字母构成的字符串,可以令a = 1,b = 2, z = 26。取一固定值M,求出该P进制数对M的余数,作为该字符串的Hash值。

字符串Hash函数把一个任意长度的字符串映射成一一个非负整数,并且其冲突概率几乎为零。

        根据经验,取P = 131P = 13331,此时Hash 值产生冲突的概率极低,只要Hash值相同,我们就可以认为原字符串是相等的。通常我们M=2^64,即直接使用unsigned long long类型存储这个Hash值,在计算时不处理算术溢出问题,产生溢出时相当于自动对2^64取模,这样可以避免低效的取模(mod) 运算。

        对字符串的各种操作,都可以直接对P进制数进行算术运算反映到Hash值上。如果我们已知字符串S的Hash 值为H(S), 那么在S后添加一个字符c构成的新字符串S+c的Hash值就是H(S +c) = (H(S)*P + value[c]) modM。其中乘P就相当于P进制下的左移运算,value[c] 是我们为c选定的代表数值。

        如果我们已知字符串S的Hash值为H(S), 字符串**S+T的Hash值为H(S +T),那么字符串T的Hash值H(T)= (H(S +T)- H(S)* plength(T) modM.**这就相当于通过P进制下在S后边补0的方式,把S左移到与S+T的左端对齐,然后二者相减就得到了H(T)。

例如:
S= “abc”, c= “d”,T= “xyz”,则:S表示为P进制数: 123
H(S)= 1P^2+2P+3

  H(S+c)=1*p3 +2*p2 +3*P+4=H(S)*P+4
  S+T表示为P进制数:123242526

  H(S+T)=1*p5+2*p4+3*p3+24*P2+25*P+26
  S在P进制下左移length(T) 位: 123000
  
  二者相减就是T表示为P进制数: 24 2526
  H(T)=H(S+T)-(1*p2 +2+P+3)*p3=24*p2 +25*P+26

根据上面两种操作,我们可以通过0(N)的时间预处理字符串所有前缀Hash值,并在0(1) 的时间内查询它的任意子串的Hash值。

题目大意:
       很久很久以前,森林里住着一群兔子。有一天,兔子们想要研究自己的DNA序列。我们首先选取一个好长好长的DNA序列(小兔子是外星生物,DNA序列可能包含26个小写英文字母),然后我们每次选择两个区间,询问如果用两个区间里的DNA序列分别生产出来两只免子,这两只兔子是否一模一样。注意两只兔子一模一样只可能是它们的DNA序列一模一样。1 <= length(S),Q <= 10^6。

思路:
       记我们选取的DNA序列为s,根据我们刚才提到的字符串Hash算法,设F[i]表示前缀子串S[1~i] 的Hash值,有F[i=F[i-1]*131+(S[i]- “a”+1).于是可以得到任一区间 [L.r] 的Hash值为F[r] - F[i-1] * 131 - i + 1。当两个区间的Hash 值相同时,我们就认为对应的两个子串相等。整个算法的时间复杂度为O([S| + Q)。

代码:

#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
const int N = 1000010;
char s[N];
ull sum[N], p[N];
int main(){
    scanf("%s", s + 1);
    int n = strlen(s + 1);
    int t;
    cin >> t;
    p[0] = 1;
    for(int i = 1; i <= n; i++){
        sum[i] = sum[i - 1] * 131 + (s[i] - 'a' + 1);
        p[i] = p[i - 1] * 131;
    }
    for(int i = 1; i <= t; i++){
        int l1, r1, l2, r2;
        cin >> l1 >> r1 >> l2 >> r2;
        if(sum[r1] - sum[l1 - 1] * p[r1 - l1 + 1] == 
        sum[r2] - sum[l2 - 1] * p[r2 - l2 + 1])
        cout << "Yes" << endl;
        else cout << "No" << endl;
    }
    return 0;
}

题目大意:

如果一个字符串正着读和倒着读是一样的,则称它是回文的。
给定一个长度为N的字符串S,求他的最长回文子串的长度是多少。
输入样例:

abcbabcbabcba
abacacbaaaab
END

输出样例:

Case 1: 13
Case 2: 6

思路:
       求出正序和逆序的Hash值, 再遍历每个点,以每个点为中心,二分查找以它为中心的回文串的最大宽度。由于要分奇偶两种情况讨论,可以在字符串的每两个字符插入一个不会用的的字符,将字符变为奇数。

列如:
abc
a#b#c
abcd
a#b#c#d

这样就可以只讨论奇数的情况。

代码:

#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int N = 2000010, base = 131;
typedef unsigned long long ull;
char s[N];
ull hl[N], hr[N], p[N];
ull get(ull h[], int l, int r){
	return h[r] - h[l - 1] * p[r - l + 1];
} 
int main(){
	int c = 1;
	while(scanf("%s", s + 1), strcmp(s + 1, "END")){
		int n = strlen(s + 1);
		for(int i = n * 2; i > 0; i -= 2){
			s[i] = s[i / 2];
			s[i - 1] = 'z' + 1;
		}
		n *= 2;
		p[0] = 1;
		for(int i = 1, j = n; i <= n; i++, j-- ){
			hl[i] = hl[i - 1] * base + s[i] - 'a' + 1;
			hr[i] = hr[ i - 1] * base + s[j] - 'a' + 1;
			p[i] = p[i - 1] * base;
 		}
 		int res = 0;
 		for (int i = 1; i <= n; i++){
 			int l = 0, r = min(i - 1, n - i);
 			while(l < r){
 				int mid = (l + r + 1) >> 1;
 				if(get(hl, i - mid, i - 1) != get(hr, n - (i + mid) + 1, n - (i + 1) + 1)) r = mid - 1;
 				else l = mid;
			 }
		 
		 if(s[i - l] <= 'z') res = max(res, l + 1);
		 else res = max(res, l);
	}
	printf("Case %d: %d\n", c++, res);
	}
	return 0;
}
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值