字符串哈希(Hash)

所谓字符串哈希就是构造一个数字使之唯一代表一个字符串。

构造方法:

假如给你一个数字1166,形式上你只知道它只是1和6的组合,但你知道它代表的实际大小1*10^3+1*10^2+6*10^1+6*10^0。

同理,给你一个字符串,要把它转换为数字,就可以先把每一个字符都先对应一个数字,然后把它们按照顺序乘以进制的幂进行相加。

如abcd,我们取各个字母的ASCII码值减去a的ASCII码值+1,便得到abcd分别对应数字1,2,3,4,接下来再取一个进制如23进行组合即可。保险起见,进制要大于每一个字符对应的数字(负数情况另外讨论)。否则会出现如下情况:

a b c d  e 分别对应 1 2 3 4  25,若此时取23进制,那么ab和e的哈希值相同,会被判断为是同一字符串。

一、例题:P3370 【模板】字符串哈希:https://www.luogu.org/problemnew/show/P3370

#include <bits/stdc++.h>

using namespace std;

typedef unsigned long long ULL;    //哈希值可能非常大

const int N=1e4+5;
const ULL B=29;    //进制

ULL hashT[N];      //用于储存每一个字符串的哈希值
char input[N];

int n,tmp;
int ans;

ULL hashf(char s[])    //生成字符串哈希值
{
    tmp=0;
    for(int i=0;i<strlen(s);i++)    //像处理字符数字的过程,也可以从右到左写
        tmp=tmp*B+(ULL)(s[i]-'a'+1);
    return tmp;
}

int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        scanf("%s",input);
        hashT[i]=hashf(input);
    }
    sort(hashT,hashT+n);
    ans=1;
    for(int i=1;i<n;i++)
    {
        if(hashT[i]!=hashT[i-1]) ans++;    //排序完哈希值不同就说明是不同的字符串
    }
    printf("%d\n",ans);
}

二、P2957 [USACO09OCT]谷仓里的回声Barn Echoes:https://www.luogu.org/problemnew/show/P2957

 与上一题不同的是,我们要的不只是一个字符串的哈希值,而是一个字符串的“滚动哈希值”,就是从第1位开始到任意位的子串的哈希值,便于之后取子串的哈希值。

#include <bits/stdc++.h>

using namespace std;

typedef unsigned long long ULL;

const int N=1e3+5;
const ULL B=29;

char s1[N],s2[N];

ULL hashT1[N],hashT2[N];    //分别储存两个字符串的滚动哈希值
ULL power[N];               //存放取字串时需要乘的幂次

int main()
{
    int ans=0,cnt1=0,cnt2=0;    
    power[0]=1;
    for(int i=1;i<=N;i++) power[i]=power[i-1]*B;
    scanf("%s\n%s",s1+1,s2+1);
    int len1=strlen(s1+1);
    int len2=strlen(s2+1);

    for(int i=1;i<=len1;i++)  //从左往右存的好处就在于取字串时只需乘进制,而不是除进制
        hashT1[i]=hashT1[i-1]*B+(ULL)(s1[i]-'a'+1);
    for(int i=1;i<=len2;i++)
        hashT2[i]=hashT2[i-1]*B+(ULL)(s2[i]-'a'+1);

    ULL hash1,hash2;
    for(int i=1,j=len2; i<=len1&&j>=1; i++,j--)    //从第一个字符串头及第二个字符串尾开始
    {
        hash1=hashT1[i];
        hash2=hashT2[len2]-hashT2[j-1]*power[i];
        if(hash1==hash2) cnt1=i;       //因为是哈希值进行比较,如果哈希值一样,则说明i前和j后是同一个字符串,i即长度
    }
    for(int i=len1,j=1; i>=1&&j<=len2; i--,j++)    //从第一个字符串尾及第二个字符串头开始
    {
        hash1=hashT1[len1]-hashT1[i-1]*power[j];
        hash2=hashT2[j];
        if(hash1==hash2) cnt2=j;
    }
    ans=max(cnt1,cnt2);
    cout<<ans<<endl;
}

 三、P1381 单词背诵:https://www.luogu.org/problemnew/show/P1381

 和之前尺取那篇博客中提到的http://acm.nefu.edu.cn/JudgeOnline/problemShow.php?problem_id=1813相似。

 思路就是求取每一个字符串的哈希值,然后利用哈希值+map进行尺取。

#include <bits/stdc++.h>

using namespace std;

typedef unsigned long long ULL;

const int N=1e3+5;
const int M=1e5+5;
const int L=15;
const int B=29;

char s1[L],s2[L];
ULL hashT1[N];
ULL hashT2[M];

int n,m;

map<ULL,int>haved;        //文章中是否出现过某单词
map<ULL,int>wordHave;     //要背的单词
map<ULL,int>sumHave;      //用于判断计算文章中有的需要背的单词的数量

int hashf(char s[])
{
    int tmp=0;
    for(int i=0;i<strlen(s);i++)
    {
        tmp=tmp*B+ULL(s[i]-'a'+1);
    }
    return tmp;
}

int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        scanf("%s",s1);
        getchar();
        hashT1[i]=hashf(s1);
        wordHave[hashT1[i]]=1;
    }
    cin>>m;
    int sumWord=0;
    for(int i=0;i<m;i++)
    {
        scanf("%s",s2);
        getchar();
        hashT2[i]=hashf(s2);
        sumHave[hashT2[i]]++;
        if(sumHave[hashT2[i]]==1 && wordHave[hashT2[i]]==1)
        {
            sumWord++;
        }
    }


    if(sumWord==0)    //特殊情况,如果文章中没有要背的单词,尺取会进行溢出操作(l直接到底)
    {                 
        printf("0\n0");
        return 0;
    }

    int wordNum=0;
    int ans=0x3f3f3f3f;
    for(int l=0,r=0;r<m;r++)
    {
        haved[hashT2[r]]++;
        if(haved[hashT2[r]]==1 && wordHave[hashT2[r]]==1) wordNum++;
        if(wordNum<sumWord) continue;
        while(haved[hashT2[l]]>1 || wordHave[hashT2[l]]==0)
        {
            haved[hashT2[l]]--;
            l++;
        }
        ans=min(ans,r-l+1);
    }
    cout<<sumWord<<'\n'<<ans<<endl;
}
发布了34 篇原创文章 · 获赞 26 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/sinat_40471574/article/details/93517539