哈希

Author Avatar
Axell 1月 19, 2019
  • 在其它设备中阅读本文章

哈希表

哈希表是由哈希函数+链表结构共同实现,链表中储存哈希值相同的元素信息,以降低将元素全部存在一个桶里的空间复杂度,同时优秀的哈希函数可以保证数据的平均分配

例题

雪花判重

见进阶指南 P63

字符串哈希

利用一个哈希函数,将任意长度的字符串映射为一个非负整数,并且冲突的几率几乎为0。
过程: 取一个质数P,把字符串看成一个P进制数,将P进制数转化为10进制数后,进行取模操作,得到该字符串的哈希值
一般,取P=131或13331,此时哈希函数出现冲突的概率极低,在有需要时,可以多取几组P和M,使得冲突概率几乎为0,只需要hash值相同,即可认为两个字符串相同。
通常,取模的M为2^64,即unsigned long long 的取值范围,可以直接用自动溢出来替代低效的取模操作。
同时,对于字符串的各种操作可以直接反应在hash值上,例如在首尾添加,取出中间一段的hash值等,均可以由原hash值计算得到

例题

兔子与兔子

见进阶指南P66,即要求判断两只兔子的DNA是否相同
代码

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;

ULL h[1000005],p[1000005];
int t=131,m,l,r,ll,rr;
char s[1000005];

inline ULL get(int l,int r){  //取出中间的一段
    return h[r]-h[l-1]*p[r-l+1];
}

bool check(int l,int r,int ll,int rr){
    ULL t=get(l,r);
    ULL tt=get(ll,rr);
    return t==tt;
}

int main(){
    scanf("%s",s+1);
    int len=strlen(s+1);
    p[0]=1;
    for (int i=1;i<=len;++i){
        h[i]=h[i-1]*t+s[i]-'a'+1;
        p[i]=p[i-1]*t;
    }
    cin>>m;
    for (int i=1;i<=m;++i){
        scanf("%d %d %d %d",&l,&r,&ll,&rr);
        if (check(l,r,ll,rr)) puts("Yes");
        else puts("No");
    }
    return 0;
}

最长回文子串

求出一段字符串的最长回文子串,做两次hash,然后枚举中心点即可,再配合二分答案
见进阶指南 P67,也可以采用manacher,在O(n)时间内得到答案

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ULL;

ULL p[500005],pr[500005],re[500005],t=13331;
int n;
char s[500005];

inline ULL getpr(int l,int r){
    return pr[r]-pr[l-1]*p[r-l+1];
}

inline ULL getre(int l,int r){
    return re[l]-re[r+1]*p[r-l+1];
}


int get(int m){
    int l=0,r=min(m-1,n-m),mid,ans;
    while (l<=r){
        mid=(l+r)>>1;
        if (getpr(m-mid,m)==getre(m,mid+m)){
            l=mid+1;
            ans=mid*2+1;
        }else{
            r=mid-1;
        }
    }
    return ans;
}

int gett(int m){
    int m2=m+1;
    int l=0,r=min(m-1,n-m2),mid,ans;
    while (l<=r){
        mid=(l+r)>>1;
        if (getpr(m-mid,m)==getre(m2,mid+m2)){
            l=mid+1;
            ans=mid*2+2;
        }else{
            r=mid-1;
        }
    }
    return ans;
}

void solve(){
    int ans=0;
    n=strlen(s+1);
    for (int i=1;i<=n;++i) pr[i]=pr[i-1]*t+s[i]-'a'+1;
    for (int i=n;i>=1;--i) re[i]=re[i+1]*t+s[i]-'a'+1;
    for (int i=1;i<=n;++i) ans=max(ans,get(i));
    for (int i=1;i<=n-1;++i) ans=max(ans,gett(i));
    printf("%d\n",ans);
}

int main(){
    p[0]=1;
    for (int i=1;i<=500000;++i) p[i]=p[i-1]*t;
    while (scanf("%s",s+1)!=EOF) solve();
    return 0;
}

后缀数组

题面见 进阶指南 P67
代码

#include <bits/stdc++.h>
using namespace std;
#define MAXN 300005
typedef unsigned long long ULL;

ULL h[MAXN],p[MAXN];
int t=131,sa[MAXN],height[MAXN],n;
char s[MAXN];

inline ULL get(int l,int r){
    return h[r]-h[l-1]*p[r-l+1];
}

int getL(int a,int b){
    int l=0,r=min(n-a+1,n-b+1),mid,ans;
    while (l<=r){
        mid=(l+r)>>1;
        if (get(a,a+mid-1)==get(b,b+mid-1)) ans=mid,l=mid+1;
        else r=mid-1;
    }
    return ans;
}

bool cmp(int x,int y){
    int tmp=getL(x,y);
    return s[x+tmp]<s[y+tmp];
}

int main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    p[0]=1;
    for (int i=1;i<=n;++i){
        h[i]=h[i-1]*t+s[i]-'a'+1;
        p[i]=p[i-1]*t;
        sa[i]=i;
    }
    sort(sa+1,sa+n+1,cmp);
    for (int i=2;i<=n;++i) height[i]=getL(sa[i],sa[i-1]);
    for (int i=1;i<=n;++i) printf("%d ",sa[i]-1);puts("");
    for (int i=1;i<=n;++i) printf("%d ",height[i]);puts("");
    return 0;
}

知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可。

本文链接:https://hs-blog.axell.top/archives/75/