最近参加了集训,学了很多新的算法。所以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 */