zoukankan      html  css  js  c++  java
  • 字符串hash入门

    简单介绍一下字符串hash

    相信大家对于hash都不陌生

    翻译过来就是搞砸,乱搞的意思嘛

    hash算法广泛应用于计算机的各类领域,像什么md5,文件效验,磁力链接 等等都会用到hash算法

    在信息学奥赛中,hash算法主要应用于搜索状态判重,字符串的比较等

    hash的主要思想是:对于一个空间、时间需求较大的状态,在一定错误率的基础上进行状态压缩,降低其时间、空间的需求量

    对于字符串hash来说,就是把一串字符串压缩成一个hash值,方便我们进行数据的处理

    接下来我们重点讲一下字符串hash的实现方法

    实现方法

    思想

    在信息学奥赛中,使用最广泛的算法叫做:BKDR Hash

    它的核心思想是:

    对于一个字符串,选取恰当的进制,将一个字符串看做是一个大整数

    (众人:***,你这是要让我们写高精啊)

    然后再对一个随便什么数取模就可以啦

    当然这个“恰当的进制”和“随便什么数”是有讲究的

    根据砖家的研究:

    进制一般选择大于字符串中的最大的字符且不含模数的值因子的数

    比如说,如果你是对一串小写字母做字符串hash,那么131这个进制就是不错的选择

    而“随便什么数”有三种方法

    1. 选择两个模数,判断的时候只有两个hash值都相同才算相同
    2. 选择一个大质数,像11111111111111111111111或者212370440130137957
    3. 用unsigned long long 自然溢出

    注意:尽量不要只用一个较小的质数,根据生日悖论,很容易被卡

    代码

    代码实现比较简单

    https://www.luogu.org/problemnew/show/3370

    • unsigned long long 自然溢出
     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<cmath>
     5 #include<cstring>
     6 #include<algorithm>
     7 #include<map>
     8 #define lli long long int 
     9 using namespace std;
    10 const int MAXN=100001;
    11 int seed=27;
    12 void read(int &n)
    13 {
    14     char c='+';int x=0;bool flag=0;
    15     while(c<'0'||c>'9'){c=getchar();if(c=='-')flag=1;}
    16     while(c>='0'&&c<='9')x=x*10+c-48,c=getchar();
    17     n=flag==1?-x:x;
    18 }
    19 char a[MAXN];
    20 map<long long ,bool>happen;
    21 int tot=0;
    22 int main()
    23 {
    24     int n;
    25     read(n);
    26     for(int i=1;i<=n;i++)
    27     {
    28         unsigned long long base=1;
    29         scanf("%s",a);
    30         for(int j=0;j<strlen(a);j++)
    31             base=base*seed+(a[j]);
    32         if(!happen[base])
    33             happen[base]=1,tot++;
    34     }
    35     printf("%d",tot);
    36     return 0;
    37 }
    ull
    • 双hash
     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 #define ull unsigned long long 
     5 using namespace std;
     6 const int MAXN=2e7+10;
     7 const int mod1=19260817;
     8 const int mod2=19660813;
     9 const int seed=233;
    10 const int seed2=133;
    11 int n;
    12 struct node
    13 {
    14     char s[1501];
    15     int h1,h2,l;
    16 }a[10001];
    17 ull gethash1(char *s,int l)
    18 {
    19     ull ans=0;
    20     for(int i=1;i<=l;i++)
    21         ans=(ans*seed+(ull)s[i])%mod1;
    22     return ans;
    23         
    24 }
    25 ull gethash2(char *s,int l)
    26 {
    27     ull ans=0;
    28     for(int i=1;i<=l;i++)
    29         ans=(ans*seed2+(ull)s[i])%mod2;
    30     return ans;
    31 }
    32 int hash1[MAXN],hash2[MAXN];
    33 int main()
    34 {
    35     scanf("%d",&n);
    36     for(int i=1;i<=n;i++)
    37     {
    38         scanf("%s",a[i].s+1);
    39         a[i].l=strlen(a[i].s+1);
    40     }
    41     for(int i=1;i<=n;i++)
    42     {
    43         a[i].h1=gethash1(a[i].s,a[i].l)%mod1;
    44         a[i].h2=gethash2(a[i].s,a[i].l)%mod2;
    45     }
    46     int ans=0;
    47     for(int i=1;i<=n;i++)
    48         if(hash1[a[i].h1]==0||hash2[a[i].h2]==0)
    49             hash1[a[i].h1]=1,hash2[a[i].h2]=1,ans++;
    50     printf("%d",ans);
    51     return 0;
    52 }
    双hash
    • 大质数hash
     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<cmath>
     5 #include<cstring>
     6 #include<algorithm>
     7 #include<map>
     8 #define lli long long int 
     9 using namespace std;
    10 const int MAXN=100001;
    11 const long long int mod=212370440130137957;
    12 int seed=27;
    13 void read(int &n)
    14 {
    15     char c='+';int x=0;bool flag=0;
    16     while(c<'0'||c>'9'){c=getchar();if(c=='-')flag=1;}
    17     while(c>='0'&&c<='9')x=x*10+c-48,c=getchar();
    18     n=flag==1?-x:x;
    19 }
    20 char a[MAXN];
    21 map<int,bool>happen;
    22 int tot=0;
    23 int main()
    24 {
    25     int n;
    26     read(n);
    27     for(int i=1;i<=n;i++)
    28     {
    29         long long base=1;
    30         scanf("%s",a);
    31         int la=strlen(a);
    32         for(int j=0;j<la;j++)
    33             base=(base*seed+(a[j]) )%mod;
    34         if(!happen[base])
    35             happen[base]=1,tot++;
    36     }
    37     printf("%d",tot);
    38     return 0;
    39 }
    大质数

    特别注意:在循环的时候,不要写: for(int j=0;j<strlen(a);j++) !!!!因为strlen的复杂度是$O(n^2)$,数据稍微一大你就会被卡T

    正确的姿势是把长度记录下来 int la=strlen(a); for(int j=0;j<la;j++) 

    常用技巧:区间hash值

    在进行字符串hash的时候,我们经常会用到某一段的hash值

    这时候怎么办呢?

    假设我们已经得到了$hash[1-r]$

    那么$hash[l-r]=hash[r]-seed^{r-l+1}*hash[l - 1]$(seed表示进制)

    举个例子

    $hash[1]=a1$

    $hash[2]=a1*base+a_2$

    $hash[3]=(a_1*seed+a_2)*seed+a_3=a_1*seed^2+a_2*seed+a_3$

    $hash[4]=[(a[1*seed+a_2)*seed+a_3]*seed+a_4=a1*seed^3+a2*seed^2+a_3*seed+a_4$

    $hash[3-4]=hash[4]-base^{2}*hash[2]=a_3*seed+a_4$

    很明显可以看出这个公式是对的

    最好自己手写一下,这样才能体会到其中的奥妙

    经典题目

    洛谷 P3370 【模板】字符串哈希  

    必做练手题,代码已经在上面给出了

    洛谷 P3375 【模板】KMP字符串匹配

    这道题可以用字符串hash水过

    http://www.cnblogs.com/zwfymqz/p/7793347.html

    洛谷P2264 情书

    这道题理论上是可以用字符串hash做的

    但是我只水到90分,各位神犇可以尝试一下

    http://www.cnblogs.com/zwfymqz/p/7355159.html

    BZOJ 3555: [Ctsc2014]企鹅QQ

     略有难度

    http://www.cnblogs.com/zwfymqz/p/7349658.html

    洛谷P3823 蚯蚓排队

    其他经典应用

    以下内容来自远航之曲大神,在此表示衷心的感谢

    1、kmp

    问题:给两个字符串S1,S2,求S2是否是S1的子串,并求S2在S1中出现的次数

    把S2 Hash出来,在S1里找所有长度为$|S2|$的子串,Hash比较。效率$O(|S1|)$

    2、AC自动机

    问题:给N个单词串,和一个文章串,求每个单词串是否是文章串的子串,并求每个单词在文章中出现的次数。

    把每一个单词hash成整数,再把文章的每一个子串hash成整数,接下来只需要进行整数上的查找即可。

    复杂度:$O(|A|2+|S|)$

    用AC自动机可以做到$O(|A|+|S|)$的复杂度,|S|是单词串总长,$|A|$是文章长度

    3、后缀数组

    问题:给两个字符串S1,S2,求它们的最长公共子串的长度。

    将S1的每一个子串都hash成一个整数,将S2的每一个子串都hash成一个整数

    两堆整数,相同的配对,并且找到所表示的字符串长度最大的即可。

    复杂度:$O(|S1|2+|S2|2)$

    用后缀数组可以优化到$O(|S|log|S|)$

    4、马拉车

    问题:给一个字符串S,求S的最长回文子串。

    先求子串长度位奇数的,再求偶数的。枚举回文子串的中心位置,然后二分子串的长度,直到找到一个该位置的最长回文子串,不断维护长度最大值即可。

    复杂度:$O(|S|log|S|)$

    用manacher可以做到$O(|S|)$的复杂度

    5、扩展kmp

    问题:给一个字符串S,求S的每个后缀与S的最长公共前缀

    枚举每一个后缀的起始位置,二分长度,求出每个后缀与S的最长公共前缀。

    复杂度:$O(|S|log|S|)$

    用extend-kmp可以做到$O(|S|)$的复杂度

    总结

    字符串hash是一个非常优秀的算法。

    希望大家能够熟练的掌握&&运用

    说不定它可以在你写不出正解的时候帮你得很多分呢?

  • 相关阅读:
    没有可用软件包 libgdiplus 解决方法
    aspnetcore2.1 部署到docker (访问出现404)
    pagination.js 使用
    System.DllNotFoundException: Unable to load DLL 'libgdiplus': The specified module could not be found.
    aspnetcore 日志 serilog-aspnetcore
    supervisor 安装配置
    HttpClient 上传图片
    C#如何调用C++(进阶篇)
    netcore使用EFcore(第一个实例)
    .NET-高并发及限流方案
  • 原文地址:https://www.cnblogs.com/zwfymqz/p/7793332.html
Copyright © 2011-2022 走看看