字符串Hash引入
下面介绍的字符串
H
a
s
h
Hash
Hash函数把一个任意长度的字符串映射成一个非负整数,并且其冲突的概率几乎为0。
取一固定值
P
P
P,把字符串看作
P
P
P进制数,并分配一个大于
0
0
0的数值,代表每种字符。一般来说,我们分配的数值都远小于
P
P
P。例如,对于小写字符构成的字符串,可以令
a
=
1
,
b
=
2
,
.
.
.
,
z
=
26
a=1,b=2,...,z=26
a=1,b=2,...,z=26。取一固定值
M
M
M,求出该
P
P
P进制数对
M
M
M的余数,作为该字符串的
H
a
s
h
Hash
Hash值。
一般来说,我们取
P
=
13331
P=13331
P=13331,此时
H
a
s
h
Hash
Hash值产生冲突的概率极低,只要
H
a
s
h
Hash
Hash值相同,我们就可以认为原字符串是相等的。通常我们取
M
=
2
64
M=2^{64}
M=264,即直接使用
u
n
s
i
g
n
e
d
l
o
n
g
l
o
n
g
unsigned\;long\;long
unsignedlonglong类型存储这个
H
a
s
h
Hash
Hash值,在计算时不处理算术溢出问题,产生溢出时相当于自动对
2
64
2^{64}
264取模,这样可以避免低效的取模(mod)运算。
除了极特殊的构造的阴间题目上,上诉
H
a
s
h
Hash
Hash算法很难产生冲突,一般情况下上诉
H
a
s
h
Hash
Hash算法完全可以出现在题目的标准解答中。(当然我们也可以多取一些
P
P
P与
M
M
M,多进行几次
H
a
s
h
Hash
Hash函数的计算,如果仍然相同才认为两个字符串相同。)
字符串Hash的操作
原理:对于字符串的各种操作,都可以直接对 P P P进制数进行算术运算反映到 H a s h Hash Hash值上。
我们记字符串 S S S的 H a s h Hash Hash值为 H ( S ) H(S) H(S),那么在 S S S后拼接一个字符 c c c构成的新字符串 S + c S+c S+c的 H a s h Hash Hash值就是 H ( S + c ) = ( H ( S ) ∗ P + v a l ( c ) ) m o d P H(S+c)=(H(S)*P+val(c))mod P H(S+c)=(H(S)∗P+val(c))modP。是不是有点类似于数和数之间的拼接?
如果我们知道字符串
S
S
S的
H
a
s
h
Hash
Hash值为
H
(
S
)
H(S)
H(S),字符串
S
+
T
S+T
S+T的
H
a
s
h
Hash
Hash值为
H
(
S
+
T
)
H(S+T)
H(S+T),那么字符串
T
T
T的
H
a
s
h
Hash
Hash值就是
H
(
T
)
=
(
H
(
S
+
T
)
−
H
(
S
)
∗
P
l
e
n
g
t
h
(
T
)
)
m
o
d
M
H(T)=(H(S+T)-H(S)*P^{length(T)}) mod M
H(T)=(H(S+T)−H(S)∗Plength(T))modM。这就相当于通过
P
P
P进制下在
S
S
S后面补
0
0
0的方式,把
S
S
S左移到与
S
+
T
S+T
S+T的左端对齐,然后两者相减就得到了
H
(
T
)
H(T)
H(T)。(如下图说明)
我们再看一下下图的例子
根据上面两种操作,我们可以通过 O ( N ) O(N) O(N)的时间预处理字符串所有前缀 H a s h Hash Hash值,并在 O ( 1 ) O(1) O(1)的时间内查询它的任意子串的 H a s h Hash Hash值。
例题训练(持续更新中)
兔子与兔子 AcWing138
题目分析:根据题意模拟字符串匹配即可,求出区间Hash值比大小。
代码之后补上……