【模板】字符串哈希
字符串哈希:将字符串映射成为整数(p进制数,模q)并保证字符串不同,得到的哈希值不同,用来判断一个子串该串是否重复出现过
就是来求字符串是否相同或者包含的
经验:p取131、13331较好, q取2^64较好(unsigned long long)
hash(i+1) = hash * p + str[i+1]
hash(L-R) = hash(R) - hash(L-1) * p^(R-L+1) 这部分可以采用数组存下来,表示p[R-L+1]
回文字符串,补#号
奇数个 abcba 补成 a#b# c #b#a 可以发现仍然是奇数个
偶数个 abba 补成 a#b # b#a 补完之后仍然是奇数个
(这样就不需要对于回文串是奇数还是偶数来分情况讨论了)
除此之外,字符串哈希常常与二分查找混合使用
字符串哈希的基本代码:
#include <iostream> using namespace std; typedef unsigned long long ULL; const int N = 100010; const int base = 131;//13331 ULL h[N],p[N]; ULL get(int l,int r){ return h[r] - h[l-1]*p[r-l+1]; } int main(){ string s; cin>>s; int len = s.length(); p[0] = 1; for(int i = 1; i <= len ; i++){ h[i] = h[i-1]*base + s[i-1] - 'a' +1;//存每一位的哈希值 p[i] = p[i-1]*base; } //如果是判断一个区间里面的字符串 int l1,l2,r1,r2; cin>>l1>>l2>>r1>>r2; if(get(l1,r1) == get(l2,r2)) cout<<"Yes"<<endl; else cout<<"No"<<endl; }
进阶:回文子串的最大长度
题目来源:https://www.acwing.com/problem/content/141/
同样是利用字符串哈希,预先处理前缀哈希和后缀哈希,再利用二分的方法,枚举中点进行匹配求解
利用类似于#的分隔符处理回文中心
二分注意二分区间,以及匹配时注意后缀哈希的处理
#include <iostream> #include <string.h> #include <algorithm> using namespace std; typedef unsigned long long ULL; const int N = 2e6+10; const int base = 131; ULL hl[N],hr[N],p[N]; ULL get(ULL h[] ,int l,int r){ return h[r] - h[l-1] * p[r-l+1]; } char s[N]; int main(){ int T = 1; while(cin>>s+1 ,strcmp(s+1,"END")){//循环只看逗号后面的数值 int len = strlen(s+1); for(int i = 2*len; i > 0;i -= 2){//倒序操作,可以节省空间 s[i] = s[i/2]; s[i-1] = 'z'+1;//这里要注意不一定要用#,但是一定要不能在a-z的范围内 } len *= 2;//长度*2 p[0] = 1; for(int i = 1,j = len; i <= len; i++,j--){ hl[i] = hl[i-1]*base + s[i] - 'a'+1; hr[i] = hr[i-1]*base + s[j] - 'a'+1; p[i] = p[i-1]*base; } int res = 0; for(int i = 1; i <= len; i++){ int l = 0, r = min(i-1,len- i); while(l < r){ int mid = l+r+1>>1;//补上+1,防止出现死循环 if(get(hl,i-mid,i-1) != get(hr,len-(i+mid)+1,len-(i+1)+1)) r = mid -1; else l = mid; } //此时长度为添加分隔符情况,需要具体求出真实长度 if(s[i - l] <= 'z') res = max(res,l+1);//字母比分隔符多1,((2*l+1)+1)/2 = l+1 else res = max(res,l); //字母比分隔符少1,(2*l+1-1)/2 = l; } cout<<"Case "<<T<<": "<<res<<endl; T++; } }
后缀数组
题目来源:https://www.acwing.com/problem/content/142/
这题也是采用了哈希字符串的思想,基本模板套路和上面的字符串匹配类似,也是采用了字符串哈希+二分的思想,降复杂度
我觉得 yxc大佬的视频讲的很好了,之后忘记了可以再重新看一遍
#include <iostream> #include <algorithm> #include <string.h> #include <climits> using namespace std; typedef unsigned long long ULL; const int base = 131; const int N = 300010; ULL h[N],p[N]; int sa[N],len; char str[N]; ULL get(int l,int r){ return h[r] - h[l-1]*p[r-l+1]; } int get_common(int a,int b){//计算最长公共前缀的长度 int l = 0 ,r = min(len- a + 1,len - b + 1); while(l < r){ int mid = (l + r + 1 )/2; if(get(a,a + mid -1) != get(b,b + mid -1)) r = mid - 1; else l = mid; } return l; } bool cmp(int a,int b){ int l = get_common(a,b); int a_val = a + l > len ? INT_MIN : str[a+l]; int b_val = b + l > len ? INT_MIN : str[b+l]; return a_val < b_val; } int main() { cin>>str+1; len = strlen(str+1); p[0] = 1; for(int i = 1; i <= len ; i++){ h[i] = h[i-1]*base + str[i]-'a'+1; p[i] = p[i-1]*base; sa[i] = i; } //对sa数组进行特定排序 sort(sa+1,sa+1+len,cmp); for(int i = 1; i <= len;i++){ cout<<sa[i]-1<<" "; } cout<<endl; for(int i = 1; i <= len ;i ++){ if(i == 1) cout<<0<<" "; else cout<<get_common(sa[i-1],sa[i])<<" "; } cout<<endl; }