哈希算法

最近参加了集训,学了很多新的算法。所以8月20号左右会开始写博客(才不是因为接下来几天要出去旅行

 

今天学习了字符串。大概学习了:哈希(Hash),KMP,Trie树,Trie图,AC自动机,后缀数组……(其实后面几个没太听懂……)

准备着先写写模板。但是发现:哈希网上的模板大多数都是对于一整个字符串make_hash。

那哈希的一个很大的优点——在 𝑂(1) 的时间内求出字符串的任意一个子串的哈希值,不就很不方便了吗?

所以自己苦思冥想(大量翻阅其他的文献)加上询问巨佬队友,总算写出了类似于前缀和的哈希!!(不过后来发现网上也有……%%%

对于字符串 S,我们常用的哈希方式是将它转化为一个整数:𝑟𝑒𝑠=𝑆[1]∗𝑥n-1+…+𝑆[𝑛] (我比较喜欢字符数组下标从1开始)

其中 x 是我们选取的一个底数(一般 res 需要对一个质数取模)。

至于自然溢出。。。现在已经构造出能够卡掉所有哈希底数的自然溢出的字符串了 (他死了)

而重点来了:在一般做题时,我们会设 f[i] 表示前缀 i 的哈希值。

 这样就能让我们在 𝑂(1) 的时间内求出字符串的任意一个子串的哈希值(要先预处理了底数的幂) 

   𝑓[𝑙…𝑟]=𝑓[𝑟]−𝑓[𝑙−1]∗𝑥^(𝑟−𝑙+1)。

哈希算法真的非常优秀,常见用法:

1、由于哈希算法非常玄学,OI 中鲜有出现以哈希为官方标解的算法。

      不过哈希往往能作为一个非常优秀的暴力。特别是与 map 等 stl 相结合时能非常方便地维护字符串的集合。

2、当然大家也可以使用“挂链”的方法维护多个字符串的哈希。。。大致是将 res 再对一个较小的数取模,将其二次映射到那个较小的值上。

3、同时,基于生日悖论,在有 √𝑚𝑜𝑑 级别的字符串时哈希就变得非常不可靠。此时可以通过取两个模数的方法来优化正确率。

   这里又涉及到一个叫“孪生素数”的东西(我是蒟蒻我不太会>_<)反正大家做题就取 109+7 和 109+9 就好了。。。

4、哈希算法还常常与二分相结合,用于求两个(或多个)字符串的最长公共 前/后 缀。

5、可以在某些时候代替后缀数组,而且这东西的常数非常优秀,有时甚至能让你达成暴力踩标算的成就 (会多一个log,但是常数真的很优秀)

接下来是代码:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const int INF=0x3f3f3f3f;
 5 const ll mod=212370440130137957ll;     // 大质数
 6 const ll base=131;                     // 底数,一般是131或233;
 7 const int Maxn=1e6+5;
 8 
 9 ll ba[Maxn];
10 ll ha[Maxn],hb[Maxn];
11 char a[Maxn] ,b[Maxn];
12 
13 int main(){
14     scanf("%s%s",a+1,b+1);
15     int lena=strlen(a+1);
16     int lenb=strlen(b+1);
17     ha[0]=0; hb[0]=0; ba[0]=1;                       // 初始化!!
18     for(int i=1;i<=max(lena,lenb);i++) ba[i]=ba[i-1]*base;      // 预处理 base^1~base^n; 
19     for(int i=1;i<=lena;i++) ha[i]=(ha[i-1]*base+(ll)(a[i]))%mod;  //make_hash;
20     for(int i=1;i<=lenb;i++) hb[i]=(hb[i-1]*base+(ll)(b[i]))%mod;
21     printf("%lld\n",ha[4]);
22     printf("%lld\n",hb[5]-hb[2-1]*ba[5-2+1]);              // hash[l...r] = hash[r]-hash[l-1]*x[r-l+1];
23     return 0;
24 }
25 /*
26 输入:
27 abcdhhhhhh
28 habcdhhhhh
29 输出:
30 219759674
31 219759674
32  */

 

posted @ 2019-08-10 19:12  pengcheng_official  阅读(296)  评论(0编辑  收藏  举报