前言:
什么是哈希?哈希其实是所有字符串操作中,最简单的操作了(哈希的过程,其实可以看作对一个串的单向加密过程,并且需要保证所加的密不能高概率重复(就像不能让隔壁老王轻易地用它家的钥匙打开你家门一样qwq),通过这种方式来替代一些很费时间的操作。 比如,最常见的,当然就是通过哈希数组来判断几个串是否相同(洛谷P3370)。此处的操作呢,很简单,就是对于每个串,我们通过一个固定的转换方式,将相同的串使其的“密”一定相同,不同的串 尽量 不同。 此处有人指出:那难道不能先比对字符串长度,然后比对ASCLL码之和吗?事实上显然是不行的(比如ab和ba,并不是同一个串,但是如是做却会让其认为是qwq)。这种情况就叫做hash冲突,并且在如此的单向加密哈希中,hash冲突的情况在所难免(bzoj就有这种让你给出一组样例,使得一段哈希代码冲突的题,读者可以尝试尝试)
下面我给出这个暑假训练时写过的hash的题。
正文:
链接:字符串哈希及kmp专题 - Virtual Judge (vjudge.net)
模板:
//预处理
pow1[0] = 1;
for (int i = 1; i <= n; i++)
pow1[i] = pow1[i - 1] * Base1 % mod;
for (int i = 1; i <= n; i++)
{
ha1[i] = (ha1[i - 1] * Base1 + s[i]) % mod;
}
//查询
long long get_hash(int l, int r)
{
long long res1 = ((ha1[r] - ha1[l - 1] * pow1[r - l + 1] % mod) + mod) %
mod;
return res1;
}
习题:
A - 字符串哈希:
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
ull base=131;
ull a[10005];
char s[10005];
int n;
ull mod=1e9+7;
ull gethash(char s[]){
int len=strlen(s);
ull ans=0;
for(int i=0;i<len;i++){
ans=(ans*base+(ull)s[i])%mod;
}
return ans;
}
int main(){
int ans=1;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",s);
a[i]=gethash(s);
}
sort(a+1,a+n+1);
for(int i=1;i<n;i++){
if(a[i]!=a[i+1])ans++;
}
cout<<ans<<endl;
return 0;
}
哈希的模板题。
B - Barn Echoes G:
#include<bits/stdc++.h>
using namespace std;
int main(){
string a,b;
cin>>a>>b;
int n=min(a.size(),b.size());
int i,j;
for(i=n;i>=1;i--){
if(a.substr(0,i)==b.substr(b.size()-i,i))break;
}
for(j=n;j>=1;j--){
if(b.substr(0,j)==a.substr(a.size()-j,j))break;
}
cout<<max(i,j)<<endl;
return 0;
}
这题我没用hash(懒),不过hash的思路还挺简单的,就是第一个字符串的前缀的区间哈希值和第二个字符串的后缀的区间哈希值或第二个字符串的前缀的区间哈希值和第一个字符串的后缀的区间哈希值比较一下,取最大值就完了。我这边直接用substr其实意思是一样的。
C - 单词背诵:
#include<bits/stdc++.h>
using namespace std;
map<string,int> mp;
map<string,bool> f;
string s[100005];
int main(){
int n,m;
cin>>n;
for(int i=1;i<=n;i++){
string x;
cin>>x;
f[x]=true;
}
cin>>m;
int l=1,ans,res;
for(int i=1;i<=m;i++){
cin>>s[i];
if(f[s[i]])mp[s[i]]++;
if(mp[s[i]]==1)ans++,res=i-l+1;
while(l<=i){
if(!f[s[l]]){
l++;
continue;
}
if(mp[s[l]]>=2){
mp[s[l]]--,l++;
continue;
}
break;
}
res=min(res,i-l+1);
}
cout<<ans<<endl;
cout<<res<<endl;
return 0;
}
这题我也没用hash,直接用map来搞定映射关系了,想要用hash的直接用我第一题的hash写进去代替map中的string就行了。
D - A-B 数对:
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
long long a[N];
map<int,int> mp;
int main(){
long long n,m;
long long ans=0;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
mp[a[i]]++;
}
for(int i=1;i<=n;i++){
ans+=mp[a[i]+m];
}
cout<<ans<<endl;
return 0;
}
这也算hash吗,map直接秒了。
E - 密文搜索:
#include <bits/stdc++.h>
using namespace std;
#define int unsigned long long
int base=131;
int t[128],a[200001];
int change(){
int cnt=1;
for(int i='a';i<='z';i++)
cnt=cnt*base+t[i];
return cnt;
}
signed main()
{
int len,n,ans=0;
string s,s1;
cin>>s>>n;
len=s.size();
for(int i=0;i<=len-8;i++)
{
memset(t,0,sizeof t);
for(int j=i;j<i+8;j++)
t[s[j]]++;
a[i]=change();
}
memset(t,0,sizeof t);
while(n--)
{
memset(t,0,sizeof t);
cin>>s1;
for(int j=0;j<8;j++)
t[s1[j]]++;
int b=change();
for(int i=0;i<=len-8;i++)
if(b==a[i]) ans++;
}
cout<<ans<<endl;
}
由于密码可能是被打乱的,所以我们需要规定一种hash可以直接找到一个合理的映射规则且不会产生冲突,我们可以根据字符序来规定hash的进制,这样就完美解决了这个问题。
I - 不重复数字:
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
inline int read(){
char c=getchar();int x=0,f=1;
for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
for(;isdigit(c);c=getchar())x=x*10+c-48;
return x*f;
}
int T,n,x;
unordered_map<int,bool>s;
void work()
{
s.clear();
n=read();
For(i,1,n){
x=read();
if(!s[x]){
printf("%d ",x);
s[x]=1;
}
}puts("");
}
int main(){
T=read();
while(T--)work();
return 0;
}
这题更是没什么好说的了,写法很多。
后记:
要开学了,新学期新气象,继续努力吧!