字符串哈希总结
1. 什么是字符串哈希
i> 将一个字符串看作一个T进制的数(每一位为字符的ASCII码) , ASCII 范围 0 - 127 ,最少 128 进制
ii> 对于这个数(很大),利用哈希函数映射到 0 ~N 的范围 RES = ( XXXXX) T
iii> 按照经验,T = 131 或 1331 ,R = 2e64 ( unsigned long long ) 时,RES mod R 产生冲突的概率极小,于是我们认为没有冲突
如果说我们用 unsigned long long 来存储 RES ,那么必然会溢出,然而溢出的效果等价于 RES mod R
iv> 对于这样一个哈希出来的值,可以等价地看作该字符串
2. 如何利用字符串哈希来提取子串
i> 预处理
处理出所有前缀字符串哈希存入h[ N] , 并更新每一位数的权重存入p[ N]
ii> 套公式
i> 将左端点的前缀字符串哈希 h[ l - 1 ] 与右端点字符串哈希 h[ r] 对齐 : h[ l - 1 ] * p[ r - l + 1 ]
ii> 再将右端点字符串哈希 h[ r] 与 对齐后的左端点的前缀字符串哈希 h[ l - 1 ] * p[ r - l + 1 ] 相减
return h[ r] - h[ l - 1 ] * p[ r - l + 1 ] ;
3. 注意点
i> 不要把某一位映射成 P 进制 0 ,例如,A 如果是 0 ,则 AA 也是 0 ,就会出现冲突
ii> \0 的 ASCII 是 0 ,本题不出现该字符,不用担心上一点
iii> 使用这种方法就假定了人品足够好,不出现冲突
核心代码
unsigned long long get_substr_hash ( int l, int r)
{
return h[ r] - h[ l - 1 ] * p[ r - l + 1 ] ;
}
void init ( )
{
p[ 0 ] = 1 ;
for ( int i = 1 ; i <= n; i ++ )
{
h[ i] = h[ i - 1 ] * T + str[ i] ;
p[ i] = p[ i - 1 ] * T;
}
}
完整代码
#include <iostream>
using namespace std;
typedef unsigned long long ULL;
const int N = 100010 , T = 131 ;
char str[ N] ;
ULL h[ N] , p[ N] ;
int n, m;
ULL get_substr_hash ( int l, int r)
{
return h[ r] - h[ l - 1 ] * p[ r - l + 1 ] ;
}
void init ( )
{
p[ 0 ] = 1 ;
for ( int i = 1 ; i <= n; i ++ )
{
h[ i] = h[ i - 1 ] * T + str[ i] ;
p[ i] = p[ i - 1 ] * T;
}
}
int main ( int argc, char * argv[ ] )
{
cin >> n >> m;
scanf ( "%s" , str + 1 ) ;
init ( ) ;
while ( m -- )
{
int l1, r1, l2, r2;
cin >> l1 >> r1 >> l2 >> r2;
if ( get_substr_hash ( l1, r1) == get_substr_hash ( l2, r2) )
puts ( "Yes" ) ;
else
puts ( "No" ) ;
}
return 0 ;
}