字符串哈希
字符串哈希可以将一个字符串转化为一个数字,可以做到加速比较的作用。一般用于比较子串
【模板】字符串哈希
这道题最简单的方式是set
友情提醒:如果真的想好好练习哈希的话,请自觉。
友情提醒:如果真的想好好练习哈希的话,请自觉。
友情提醒:如果真的想好好练习哈希的话,请自觉。
所以我们用哈希。
于是我们可以在输入之后算出哈希值,查询此哈希值是否出现过。如果没有就给答案加一。
我写的是自然溢出、双哈希。
自然溢出就是使用unsigned long long
或unsigned int
,就拿unsigned long long
举例,使用unsigned long long
如果结果超出unsigned long long
的范围那么结果就会对
2
64
2^{64}
264取模。
双哈希就是每个字符串有两个哈希值,这样哈希碰撞的概率就会减小。
哈希碰撞是两个不一样的字符串的哈希值一样,导致答案错误。
求哈希值代码:
unsigned long long get_hash(string s,int x)//x代表第多少个进制数
{
unsigned long long sum=0;//自然溢出
for(int i=0;i<s.size();i++)//枚举字符串的每一位
{
sum=(sum*b[x]+s[i]);//b[x]进制
}
return sum%mod;//模mod不然会爆数组
}
完整代码(无注释)方便粘贴
#include<bits/stdc++.h>
using namespace std;
const int mod=998245,b[3]={0,13331,131};
int n,cnt;
bool f[10000005][3];
string s;
unsigned long long get_hash(string s,int x)
{
unsigned long long sum=0;
for(int i=0;i<s.size();i++)
{
sum=(sum*b[x]+s[i]);
}
return sum%mod;
}
signed main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>s;
if(f[get_hash(s,1)][1]&&f[get_hash(s,2)][2])continue;
f[get_hash(s,1)][1]=1;
f[get_hash(s,2)][2]=1;
cnt++;
}
cout<<cnt;
return 0;
}
P10468 兔子与兔子
这道题其实就是让我们每次取两个子串来进行比较。
所以这次我们需要求出子串的哈希值。
我们可以使用前缀和的思想。
所以我们可以得到一个公式hs[r][x]-hs[l-1][x]*pw[r-l+1][x]
hs[i][x]表示在进制数b[x]下1~i的哈希值,pw[i][x]是b[x]的i次方。
所以代码又写完了。
完整代码
#include<bits/stdc++.h>
using namespace std;
const int b[3]={0,911,131};
int n,m,l1,r1,l2,r2;
unsigned long long hs[1000005][3],pw[1000005][3];
string s;
void init_hash(string s,int x)
{
pw[0][x]=1;
for(int i=1;i<=n;i++)
{
pw[i][x]=pw[i-1][x]*b[x];
hs[i][x]=hs[i-1][x]*b[x]+s[i];
}
}
unsigned long long get_hash(int l,int r,int x)
{
return hs[r][x]-hs[l-1][x]*pw[r-l+1][x];
}
int main()
{
cin>>s;
n=s.size();
s=" "+s;
cin>>m;
init_hash(s,1);
init_hash(s,2);
for(int i=1;i<=m;i++)
{
cin>>l1>>r1>>l2>>r2;
if(get_hash(l1,r1,1)==get_hash(l2,r2,1)&&get_hash(l1,r1,2)==get_hash(l2,r2,2))
{
cout<<"Yes\n";
}
else
{
cout<<"No\n";
}
}
return 0;
}
这题和上一道题的区别就是要预处理出哈希值数组和b[x]的幂次,以便O(1)求出区间哈希值。
CF39J Spelling Check CF
CF39J Spelling Check VJ
题目简化:有几种删除方式能使字符串变成目标字符串,并输出删除的位置。
于是我们可以推一个删除一个字符之后的哈希值的式子。
return hs[pos-1][x]*pw[l-pos][x]+get_hash(pos+1,l,x);
get_hash是一段的哈希值。
完整代码(命名有点乱)
#include<bits/stdc++.h>
using namespace std;
const int mod=999833,b[5]={0,911,131,13331,9191};
unsigned long long lt1,lt2,l,cnt;
unsigned long long hs[1000006][3],pw[1000006][3];
string s,t;
void hack(string s,int x)
{
pw[0][x]=1;
for(int i=1;i<=l;i++)
{
hs[i][x]=(hs[i-1][x]*b[x]+s[i]);
pw[i][x]=pw[i-1][x]*b[x];
}
}
unsigned long long make(string s,int x)
{
unsigned long long sum=0;
for(int i=1;i<s.size();i++)
{
sum=(sum*b[x]+s[i]);
}
return sum;
}
unsigned long long get_hash(int l,int r,int x)
{
return hs[r][x]-hs[l-1][x]*pw[r-l+1][x];
}
unsigned long long dis(int pos,int x)
{
return hs[pos-1][x]*pw[l-pos][x]+get_hash(pos+1,l,x);
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>s>>t;
l=s.size();
s=" "+s;
hack(s,1);
hack(s,2);
t=" "+t;
lt1=make(t,1);
lt2=make(t,2);
for(int i=1;i<=l;i++)
{
if(dis(i,1)==lt1&&dis(i,2)==lt2)
{
cnt++;
}
}
cout<<cnt<<'\n';
for(int i=1;i<=l;i++)
{
if(dis(i,1)==lt1&&dis(i,2)==lt2)
{
cout<<i<<' ';
}
}
return 0;
}