字符串哈希——解决KMP等问题

字符串哈希邪教(不保证正确性),解决一些奇怪的字符串哈希问题

字符串哈希

一开始以为这个是一个比较难的东西,最近学了下,发现是个挺傻屌的东西的。很好理解。

所谓\(hash\),就是希望有这么一个函数value hash( const string &s ) 对于给定的每一个字符串,都能返回给我一个值。我们利用这个值,可以把长度为\(n\)的串,本来需要\(O(n)\)的去比较一次,现在用这个值可以只进行一次比较得出结果。

其实我们可以把这个映射,看成是\(BASE\)进制下的一种操作。对于一个字符串长度为\(n\)的话,我们就要用\(n\)\(BASE\)进制去表示这个值。这个很好表示:

\[ BASE^{n} * s[n] + BASE^{n-1} * s[n-1] + ... + BASE^{1} * s[1] \]

注意,字符串哈希尽可能避免其中有\(0\)的出现,如果将\(a\)映射为\(0\),那么\(ab\)\(b\)可能会出现一样的结果

我们很难有那么多位去表示一个串,如果高精度的话,那么和直接去比较也没什么区别了。所以我们可以取模一个数,看成是\(mod\ P\)意义下的映射表示。

具体的\(hash\)函数:

1
2
3
4
5
6
7
ull hashh( char s2[] , int n )
{
ull ans = 0 ;
for( int i = 1 ; i <= n ; i++)
ans = ans * BASE + s2[i] ;
return ans ;
}

这样,我们就可以给出一个字符串进去,然后出来一个\(hash\)值了。

如果是需要涉及子串的问题,我们可以这样:

1
2
3
4
5
6
7
void hashh( ull ans[] , char s1[] , int n )
{
for( int i = 1 ; i <= n ; i++)
{
ans[i] = ans[i-1] * BASE + s1[i] ;
}
}

维护一个像前缀和一样的东西就就可以了。 这里是需要取余的,我这里没有,是利用了unsigned类型的数字,溢出会自动取余的性质和unsigned long long 刚好是一个素数的性质 如果不用素数取余,错误概率将大大提高。为了减少冲突概率,还可以使用双取余等各种其他方法(详情见参考资料)。如果自己手写处理\(hash\)冲突,那么其实这种做法就没有什么优点了。

对于这个,子串如何去使用呢? 一开始我以为,我只需要简单的对于截取子串\(l...r\)的哈希值,使用ans[r] - ans[l] 就可以了。结果实际使用中发现崩了。

想了想才明白,我们应该将他看成是\(r\)位和\(l\)位的\(BASE\)进制下的数,我现在想截取前\(l\)位(想一下,每一位对应于第几位)。 所以,我们需要进行一个简单的变形: \[ ans[r] - ans[l] * BASE^{r-l+1} \]

我当时这里卡了一下。可以类比于我有\(12345\)\(123\), 需要把\(45\)提出,是不是需要乘上一些进制,把\(123\)变成\(12300\)? 我一开始以为\(hash\)出来的值,最高位存的是\(s[n]\),后来想了下构建过程,构建是从\(1...n\),然后又不断的乘\(BASE\),其实是把最低位给送到了\(hash\)出来的值的最高位。

遇到的问题: - 如何证明\(hash\)冲突的概率是多大?

相关题目

HDU 1686

题意: 给出串\(T\)和串\(S\),询问串\(T\)在串\(S\)中出现了几次(允许有重叠部分,具体参考样例)

HDU 1711

题意:给出数列\(S\)和数列\(T\),求\(T\)在数列\(S\)中第一次出现的位置。

HDU 2097

题意: 给出串\(s\)和串\(t\),求串\(t\)在串\(s\)中出现了几次(无重叠)

HDU 5510

题意:找出一个串,这个串前面的至少\(1\)个串不是该串的子串,且该串给出的顺序最靠后,求该串是第几个给出的串?

这道题时间复杂度挺高的,我第一眼以为是\(AC\)自动机。但是加上剪枝的话,时间复杂度上界没变,下界小很多,想卡这种做法似乎很难(听说剪枝后暴力都可过)。 这是\(ACM-ICPC 2015\)沈阳的题,我在想现场赛遇到这种题,敢不敢写,该怎么写啊?

参考资料



本文标题:字符串哈希——解决KMP等问题

文章作者:Xie Keyi

发布时间:2018年04月26日 - 17:04

原始链接:https://xiekeyi98.com/d9000e89.html

许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 转载请保留原文链接及作者。