转自https://blog.csdn.net/Mikchy/article/details/103995537
例题https://leetcode-cn.com/problems/distinct-echo-substrings/submissions/
1.自然溢出法
对于自然溢出方法,我们定义 Base ,而MOD对于自然溢出方法,就是 unsigned long long 整数的自然溢出,相当于MOD 是
#include<iostream>//自然溢出哈希 求解对称子串问题 17行展示遍历子串的技巧
#include<set>//题目https://leetcode-cn.com/problems/distinct-echo-substrings/
#include<string>//解析https://blog.csdn.net/Mikchy/article/details/103995537
#define ull unsigned long long
using namespace std;
const int N=1e6+10;
ull base=29;//基数要选素数
ull hash[N],p[N];//哈希数组,基数幂数组
set<ull> h;//存放答案
int solution(string text){
int n=text.size();
hash[0]=0,p[0]=1;
for(int i=0;i<n;i++)
hash[i+1]=hash[i]*base+(text[i]-'a'+1);
for(int i=1;i<n;i++)
p[i]=p[i-1]*base;
for(int len=2;len<=n;len+=2){//遍历长度为len的子串是否对称
for(int i=0;len+i-1<n;i++){//从字符串开头遍历到结尾 字符串为[i,i+len-1] 计算方法:x-i+1=len
int l1=i,r1=i+(len>>1)-1;//左半部分为[i,len/2+i-1] 计算方法:x-i+1=len/2
int l2=i+(len>>1),r2=i+len-1;//右半部分为[len/2+i,i+len-1]
ull left=hash[r1+1]-hash[l1]*p[r1-l1+1];
ull right=hash[r2+1]-hash[l2]*p[r2-l2+1];
if(left==right) h.insert(left);
}
}
return h.size();
}
int main(){
string text;
cin>>text;
cout<<solution(text);
return 0;
}
2.单Hash
定义了 Base 和 MOD,有了对应的要求余 MOD。所以一般用 long long 就可以了。
#include<iostream>//单哈希 求解对称子串问题
#include<set>//题目https://leetcode-cn.com/problems/distinct-echo-substrings/
#include<string>//解析https://blog.csdn.net/Mikchy/article/details/103995537
#define ll long long
using namespace std;
const int N=1e6+10;const ll MOD = 1e9 + 7;
ll base=29;//基数要选素数
ll hash[N],p[N];//哈希数组,基数幂数组
set<ll> h;//存放答案
int solution(string text) {
int n=text.size();
hash[0]=0,p[0]=1;
for(int i=0; i<n; i++)
hash[i+1]=(hash[i]*base+(text[i]-'a'+1))% MOD;
for(int i=1; i<n; i++)
p[i]=(p[i-1]*base)% MOD;
for(int len=2; len<=n; len+=2) { //遍历长度为len的子串是否对称
for(int i=0; len+i-1<n; i++) { //从字符串开头遍历到结尾 字符串为[i,i+len-1] 计算方法:x-i+1=len
int l1=i,r1=i+(len>>1)-1;//左半部分为[i,len/2+i-1] 计算方法:x-i+1=len/2
int l2=i+(len>>1),r2=i+len-1;//右半部分为[len/2+i,i+len-1]
ll left = ((hash[r1 + 1] - hash[l1] * p[r1 + 1 - l1]) % MOD + MOD) % MOD;
ll right = ((hash[r2 + 1] - hash[l2] * p[r2 + 1 - l2]) % MOD + MOD) % MOD;
if(left==right) h.insert(left);
}
}
return h.size();
}
int main() {
string text;
cin>>text;
cout<<solution(text);
return 0;
}
3.双Hash
将一个字符串用不同的Base和MOD,hash两次,将这两个结果用一个二元组表示,作为一个总的Hash结果。
这个方法可以很有效的避免出现冲突。 结果再用pair来存就可以了,之后比较两个二元组是否相同。
#include<iostream>//双哈希 求解对称子串问题
#include<set>//题目https://leetcode-cn.com/problems/distinct-echo-substrings/
#include<string>//解析https://blog.csdn.net/Mikchy/article/details/103995537
#include<map>
#define ll long long
using namespace std;
const int N=1e6+10;const ll MOD1 = 1e9 + 7,MOD2 = 1e9 + 9;
ll base1=29,base2=131;//基数要选素数
ll hash1[N],p1[N],hash2[N],p2[N];//哈希数组,基数幂数组
set<pair<ll,ll> > h; // 因为是一个二元组,所以可以用 pair 容器。
int solution(string text) {
int n=text.size();
hash1[0]=0,p1[0]=1,hash2[0]=0,p2[0]=1;
for(int i=0; i<n; i++){
hash1[i+1]=(hash1[i]*base1+(text[i]-'a'+1))% MOD1;
hash2[i+1]=(hash2[i]*base2+(text[i]-'a'+1))% MOD2;
}
for(int i=1; i<n; i++){
p1[i]=(p1[i-1]*base1)% MOD1;
p2[i]=(p2[i-1]*base2)% MOD2;
}
for(int len=2; len<=n; len+=2) { //遍历长度为len的子串是否对称
for(int i=0; len+i-1<n; i++) { //从字符串开头遍历到结尾 字符串为[i,i+len-1] 计算方法:x-i+1=len
int l1=i,r1=i+(len>>1)-1;//左半部分为[i,len/2+i-1] 计算方法:x-i+1=len/2
int l2=i+(len>>1),r2=i+len-1;//右半部分为[len/2+i,i+len-1]
ll left1 = ((hash1[r1 + 1] - hash1[l1] * p1[r1 + 1 - l1]) % MOD1 + MOD1) % MOD1;
ll left2 = ((hash2[r1 + 1] - hash2[l1] * p2[r1 + 1 - l1]) % MOD2 + MOD2) % MOD2;
ll right1 = ((hash1[r2 + 1] - hash1[l2] * p1[r2 + 1 - l2]) % MOD1 + MOD1) % MOD1;
ll right2 = ((hash2[r2 + 1] - hash2[l2] * p2[r2 + 1 - l2]) % MOD2 + MOD2) % MOD2;
if(left1 == right1 && left2 == right2) h.insert(make_pair(left1, left2));
}
}
return h.size();
}
int main() {
string text;
cin>>text;
cout<<solution(text);
return 0;
}
求子串的Hash值
直接看可知S的[1,2]的子串就等于S1,所以它们对应的Hash应该相同。 所以要将之前的所有系数都消掉。
记得结果要取模运算