zoukankan      html  css  js  c++  java
  • 【暖*墟】#Hash# 字符串Hash与例题

    【字符串Hash】

    1.特征与理解

    用于寻找字符组出现的位置次数的问题,即【字符串匹配问题】。

    2.滚动哈希的优化技巧

    选取两个合适的互质数b,h(b<h),假设字符串C=c1c2...cm,

    定义哈希函数为:H(C)=(c1*b^(m-1)+c2*b^(m-2)+....+cm*b^0) mod h

    b为基数,H(C)的处理相当于把字符串看成b进制数。

    这一过程通过递归计算:H(C,k)=H(C,k-1)*b+ck

    判断某段字符与另一匹配串是否匹配,即判断:

    (某段字符:从位置k+1开始的长度为n的子串C'=ck+1ck+2....ck+n)

    H(C')=H(C,k+n)-H(C,k)*b^n 与 H(S) 的关系。

    ---> 预处理字符串所有前缀Hash值。

    ---> 在O(1)时间内查询它的任意子串Hash值。

    对unsigned long long的使用:

    可以用于取模2^64。遇到这种限制条件时就要想到用unsigned long long类型。

    可以简洁地声明为typedef unsigned long long ull。因为ull的范围是[0,2^64-1]。

    ( 2^64:18446744073709551616,即10^19。)

    所以,如果ull类型的整数溢出了,就相当于取模2^64了。

    而long long的范围是[-2^63,2^63-1],因为有符号的第63位表示“正负”而不表示数值

    3.例题与运用

    (1) poj3461 Oulipo

    //qwq
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <stack>
    #include <queue>
    using namespace std;
    typedef unsigned long long ull;
    
    /*【Oulipo】poj3461
    给你两个字符串s1和s2,求出s1在s2中出现的次数。 */
     
    //字符串Hash的模板题
    
    const int b=13331; //base,基数
    
    ull power[1000009]; //存储b^n
    void init(){
        power[0]=1; //b^0=1
        for(int i=1;i<1000000;i++)
            power[i]=power[i-1]*b; //对ull取模
    }
    
    char s1[10009],s2[1000009];
    ull sum[1000009]; //记录主串哈希值的数组
    
    int main(){
        init(); int T; cin>>T;
        while(T--){
            scanf("%s%s",s1+1,s2+1); //从第一位开始输入
            int n=strlen(s1+1),m=strlen(s2+1);
            sum[0]=0; //记录主串哈希值的数组
            for(int i=1;i<=m;i++) //计算主串的滚动Hash值
                sum[i]=sum[i-1]*b+(ull)(s2[i]-'A'+1);
            ull s=0;
            for(int i=1;i<=n;i++) //计算匹配串的Hash值
                s=s*b+(ull)(s1[i]-'A'+1); 
            int ans=0;
            for(int i=0;i<=m-n;i++)
                if(s==sum[i+n]-sum[i]*power[n]) ans++;
            printf("%d
    ",ans);
        }
        return 0;
    }

    (2)poj2406 Power Strings

    //qwq
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <stack>
    #include <queue>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    /*【power strings】poj2406
    给出一个不超过1e6的字符串,求这个字符串最多有多少个周期。 */
     
    //字符串Hash的模板题
    
    const int b=131; //base,基数
    const int mod=10009; //h,要mod的数
    int len;
    ull hashs[1001000];
    
    ull cal(int x,ull y){ //y^x
        ull now=1;
        while(x){
            if(x&1) now=(now*y)%mod;
            x>>=1; y=(y*y)%mod;
        }
        return now;
    }
    
    bool check(int x){ //x:最小的循环长度
        ll cc=cal(x,(ull)b);
        for(int i=(x<<1);i<=len;i+=x)
            if((hashs[i]-(hashs[i-x]*cc)%mod+mod)%mod!=hashs[x]) 
            //求H(C,k+n)-H(C,k)*b^n,之后的每一段长度的hash值是否匹配
            //加上mod,防止相减为负值
                return false;
        return true;
    }
    
    int main(){
        while(1){
            char s[1001000];
            scanf("%s",s+1);
            len=strlen(s+1);
            if(len==1&&s[1]=='.') return 0;
            for(int i=1;i<=len;i++) //求出Hash值
                hashs[i]=(hashs[i-1]*b+s[i])%mod;
            for(int i=1;i<=len;i++) //枚举循环的长度
            //↓↓↓最小的循环长度对应最大的周期,即第一个满足的就是最大周期数
                if(len%i==0 && check(i)){ 
                    printf("%d
    ",len/i); break; 
                }
        }
        return 0;
    }

    (3)poj2752

    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <stack>
    #include <queue>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    /*【既是前缀又是后缀的子串】POJ 2752 //kmp
    从大到小输出所有既是前缀又是后缀的字符串的长度。  */
    
    //该前缀串的最后一个字符肯定与s的最后一个字符相同。
    //从n - 1位既最后一位开始回滚,若s[next[n-1]] == s[n-1],
    //则子串s[0,1,2,...,next[n-1]]是满足条件的子串。
    //然后判断s[next[next[n-1]]] == s[n-1]是否成立,
    //这样一直回滚,直到next[next[.....next[n-1]]] == -1为止。
     
    int Next[400005],ans[400005];
    char str[400005];
    int cnt,len;
     
    void getNext(){
        Next[0] = -1; //起点
        int i = 0, j = -1;
        while (i < len){ //设置终点
            if (j == -1 || str[i] == str[j]){ 
            //↑↑↑找不到任何之前的匹配,但这一位可以匹配
                i++; j++; Next[i]=j; //给next赋值
            }
            else j = Next[j]; //回溯到上一个next寻找匹配
        }
    }
     
    int main(){
        while (scanf("%s", str) != EOF){
            len = strlen(str);
            getNext(); //预处理建立next数组
            cnt = 0; //存答案的种类数
            int j = Next[len - 1]; //用next不断向前滚动
            while (j != -1){ 
                if (str[j] == str[len - 1]) 
                    ans[cnt++] = j + 1; //匹配串长度
                j = Next[j];
            }
            for (int i = cnt - 1; i >= 0; --i)
                printf("%d ", ans[i]); //从ans[0]开始记录的
            printf("%d
    ", len);
        }
        return 0;
    }
    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    using namespace std;
    typedef unsigned long long UL;  //hash
    
    UL hashs[400005],mul[400005],K=31,P=1000000031;
    char s[400005];
    
    int main(){
        mul[0]=1;
        for(int i=1;i<=400000;i++) //预处理k^n
            mul[i]=mul[i-1]*K%P;
        while(~scanf("%s", s+1)){
            int n=strlen(s+1);
            for(int i=1;i<=n;i++) //求hash前缀和
                hashs[i]=(hashs[i-1]*K+s[i]-'a'+1)%P;
            for(int i=1;i<=n;i++)
                if(hashs[i]==((hashs[n]-(hashs[n-i]*mul[i]%P))+P)%P) //前缀=后缀
                    printf("%d ", i);
            putchar('
    ');
        }
        return 0;
    }

     

    (4)bzoj3916 friends

    //qwq
    #include <cmath>
    #include <iostream>
    #include <cstdio>
    #include <string>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <stack>
    #include <queue>
    #include <map>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    
    /*(bzoj3916)【friends】【hash】
    有三个好朋友喜欢在一起玩游戏,A君写下一个字符串S,
    B君将其复制一遍得到T,C君在T的任意位置(包括首尾)插入一个字符得到U.
    现在你得到了U,请你找出S.  */
    
    const int P=131;
    const int N=2000010;
    ull h[N],t[N];
    map<ull,int>q;
    char ss[N];
    
    int main(){
        int n,ans,len; scanf("%d",&n);
        if(n%2==0||n==1){ puts("NOT POSSIBLE"); return 0; }
        len=(n-1)/2; scanf("%s",ss+1);
        t[0]=1;h[0]=0;
        for(int i=1;i<=n;i++) t[i]=t[i-1]*P; //预处理P^n
        for(int i=1;i<=n;i++) h[i]=h[i-1]*P+(ull)ss[i]; //hash前缀和
        for(int i=1;i<=n;i++){
            if(i<=len){
                ull l=h[i-1],mid=h[len+1]-t[len+1-i]*h[i],r=h[n]-h[n-len]*t[len];
                l=l*t[len-i+1]+mid;
                if(l==r){
                    if(q[l]||(!ans)) ans=i,q[l]=1;
                    else{ puts("NOT UNIQUE"); return 0; }
                }
            }
            if(i==len+1){
                ull l=h[i-1],r=h[n]-t[len]*h[i];
                if (l==r){
                    if(q[l]||(!ans)) ans=i,q[l]=1;
                    else{ puts("NOT UNIQUE"); return 0; }
                } 
            }
            if(i>len+1){
                ull l=h[len],mid=h[i-1]-h[len]*t[i-1-len],r=h[n]-h[i]*t[n-i];
                r=mid*t[n-i]+r; 
                if(l==r){
                    if(q[l]||(!ans)) ans=i,q[l]=1;
                    else{ puts("NOT UNIQUE"); return 0; }
                } 
            }
        }
        if(!ans){ puts("NOT POSSIBLE"); return 0; }
        else{ 
            int t=1;
            while(len--){
                if(t==ans) t=t+1;
                printf("%c",ss[t]);
                t++;
            }
        }
        return 0;
    }
  • 相关阅读:
    flask中程序和请求上下文
    flask的初始化
    git 强制覆盖本地代码
    python编写一个带参数的装饰器
    Android 11 unexpected LOCAL_MODULE_CLASS for prebuilts: FAKE
    systemctl自定义service执行shell脚本时报错:code=exited, status=203/EXEC
    shell应用记录
    ssm在maven项目中的需要的依赖
    swiper 5张卡片轮播图实现效果
    Codeforces 1534 题解
  • 原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/9334911.html
Copyright © 2011-2022 走看看