前言
蒟蒻最近在复习字符串算法...但正如之前所说,我OI太菜被关起来了,本蒟蒻只能从最简单的哈希入手了TAT。而别的dalao都在学习AC自动机/后缀数组等高到不知哪里去的算法qwq。
基本思想
映射。把一个任意长度的字符串映射为一个非负整数,要求冲突概率几乎为0。方法是把字符串看成$p$进制数,通常取$p$为131或13331,当然还有特殊情况,如[CTSC2014]企鹅QQ这道题,之后会解释这种情况。
基本操作
*****************采用unsigned long long存储哈希值和131的幂次***********************
一般情况下,我们都是预处理出字符串前缀子串的哈希值,如下。复杂度是$O(n)$的。
1 for(int i=1;i<=len;i++) 2 f[i]=f[i-1]*131+ch[i]; 3 //ch[]是字符数组
当然也可以边用边求啦qwq,主要用于带修改的情况,如[USACO15FEB]审查(黄金)Censoring (Gold),就不能预处理出来,因为随时可能会有删除字符的操作qwq。
调用一个字符串子串$S[l,r]$的哈希值。复杂度是$O(1)$的。$p$数组是131的幂次,可提前预处理出来,注意$p[0]=1$。以及注意$p$数组若需要预处理,一定处理到字符串的最大长度。
1 ull gethash(int l,int r) 2 { 3 return f[r]-f[l-1]*p[r-l+1]; 4 }
如果我们想要得到同一个字符串中的两个子串拼接得到的串的哈希值?
从这个问题开始,我们并不需要死记硬背,而是切身的把字符串当做一个数,用进制的思想解决。举第一个栗子:
在[CTSC2014]企鹅QQ这道题中,我们每次都在枚举把哪一个位置的字符去掉,然后将这个字符左边的字符串和右边的字符串重新拼接成一个新的字符串。
假设我们现在有"$zsyasjttql$“这个字符串,我们现在删去第4个位置的“a”,欲得到“$zsysjttql$”,而且我们已经处理了所有前缀的哈希值。那么我们就可以用第一个子串的哈希值乘$131^{len-4}$,类似把一个数分开的操作,在加上第二个字符串的哈希值,就能得到新字符串的哈希值。
Warning?
$strlen$操作是$O(n)$的?所以尽量不要多次调用,而是一次解决。一不小心在循环里可能就会搞成$O(n^2)$的qwq.
$hash$是关键字啦qwqwq,还是尽量避讳的好。
例题
例1
LuoguP3121 [USACO15FEB]审查(黄金)Censoring (Gold)【Hash做法】By cellur925
例2
[CTSC2014]企鹅QQ
给 n 个字符串,如果两个字符串只有同一个位置的字符不相同,那
么称这两个字符串是相似的。
字符串的长度都相等,并且字符串两两不同。
求一共有多少对相似字符串。
n ≤ 30000; |Si| ≤ 200,时间限制 2 秒。
可以枚举删去字符的位置,然后再比较他们的哈希值。
Code
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 5 using namespace std; 6 typedef unsigned long long ull; 7 8 int n,l,s; 9 long long ans; 10 char tmp[500]; 11 ull f[30090][250],p[300],tong[30090]; 12 13 ull gethash(int u,int l,int r) 14 { 15 return f[u][r]-f[u][l-1]*p[r-l+1]; 16 } 17 18 int main() 19 { 20 p[0]=1; 21 scanf("%d%d%d",&n,&l,&s); 22 for(int i=1;i<=n;i++) 23 { 24 scanf("%s",tmp+1); 25 int len=strlen(tmp+1); 26 for(int j=1;j<=len;j++) 27 f[i][j]=f[i][j-1]*163+tmp[j],p[j]=p[j-1]*163; 28 } 29 for(int pos=1;pos<=l;pos++) 30 { 31 for(int i=1;i<=n;i++) 32 { 33 ull ha1=gethash(i,1,pos-1); 34 ull ha2=gethash(i,pos+1,l); 35 tong[i]=ha1*p[l-pos]+ha2; 36 } 37 sort(tong+1,tong+1+n); 38 int noww=1; 39 for(int pos=2;pos<=n;pos++) 40 if(tong[pos]==tong[pos-1]) ans+=noww,noww++; 41 else noww=1; 42 } 43 printf("%lld",ans); 44 return 0; 45 }
总结
哈希还是很简单的知识,但是需要灵活运用。只要把字符串当做一个纯洁的数字,再按照数的方式构造乱搞就行了。虽然我不会别的高级字符串算法(逃),但是灵活使用哈希也可以补充智商不够没学过那么多算法的缺陷嘛(逃)