• 哈希学习笔记


    hash学习笔记

    1. 常用函数: $ hash[i] = sum _{j=i} ^{len-1} {s[j]*X^{j-i}}, X ≥ |字符集| $ 取多个模,对于一个子串(s[i]s[i+1]..s[j])(hash = hash[i] - hash[j+1]*X^{j-i+1}),预处理(hash[i])以及(X^i)即可(O(1))求出所有的子串hash值
    2. 一般运用hash进行快速查找,状态压缩,其他hash相关:康托展开及逆展开,ELFhash

    Problem A

    1. 题意:给定一字符串S,询问(LCP(x,y)),即从x,y分别开始的最长公共前缀
    2. 做法:二分最长公共前缀的长度L,(O(1))判断两个串的(hash)是否相同
    #include <bits/stdc++.h>
    #define rep(i,a,b) for(int i=a;i<=b;++i)
    #define frep(i,a,b) for(int i=a;i>=b;--i)
    typedef unsigned long long ll;
    const int N = 100007;
    const ll base = 107;
    using namespace std;
    int n,q;
    ll P[11][N], h[11][N], mod[11];
    int cc;
    char s[N];
    void init_hash() {
        cc=0;
        mod[++cc] = 1e9+7;
        mod[++cc] = 1000007;
        mod[++cc] = 998244353;
        mod[++cc] = 100007;
    
        P[0][0]=1; rep(i,1,100) P[0][i]=P[0][i-1]*base;
        frep(i,n,1) h[0][i] = h[0][i+1]*base + s[i];
    
        rep(i,1,cc){
            P[i][0]=1;
            rep(j,1,100) P[i][j]=(P[i][j-1]*base)%mod[i];
        }
        rep(i,1,cc)frep(j,n,1)h[i][j] = ((h[i][j+1]*base)%mod[i]+s[j])%mod[i];
    }
    ll ask0(int x,int y) {
        if(x>y)swap(x,y);
        return h[0][x]-h[0][y+1]*P[0][y-x+1];
    }
    ll ask1(int x,int y,int z) {
        if(x>y)swap(x,y);
        return (h[z][x]%mod[z]-(h[z][y+1]*P[z][y-x+1])%mod[z]+mod[z])%mod[z];
    }
    int ck(int x,int y,int L) {
        if(ask0(x,x+L-1)!=ask0(y,y+L-1))return 0;
        rep(i,1,cc){
            if(ask1(x,x+L-1,i)!=ask1(y,y+L-1,i)) return 0;
        }
        return 1;
    }
    int solve(int x,int y) {
        int l=1,r=min(n-x+1,n-y+1),mid,ans=0;
        if(s[x]!=s[y])return 0;
        while(l<=r) {
            mid = (l+r)>>1;
            if(ck(x,y,mid))l=mid+1,ans=mid;
            else r=mid-1;
        }
        return ans;
    }
    
    int main() {
        scanf(" %s",s+1);
        n=strlen(s+1);
        init_hash();
        scanf("%d",&q);
        while(q--) {int x,y;
            scanf("%d %d",&x,&y);
            printf("%d
    ",solve(x,y));
        }
        return 0;
    }
    
    

    Problem B

    1. 题意:给定一字符串S,1)询问(LCP(x,y)),即从x,y分别开始的最长公共前缀;2)修改位置x的字符为y
    2. 做法1:线段树维护维护每个位置的hash[i],修改操作相当于给区间[1,x]加上

    [(y-s[x])*X^x,(y-s[x])*X^x-1,...(y-s[x])*X^0 ]

    这一等比数列,查询就是单点查询,再用上一题的做法即可。区间加公比固定的等比数列
    3. 做法2:线段树维护(S[i]*X^i)修改就是单点修改,查询([i,j])区间和之后要除以(X^i),要预处理对应模数的逆元。代码回头补。


    Problem C [BZOJ1014]

    1. 题意:给定一字符串S,1)询问(LCP(x,y)),即从x,y分别开始的最长公共前缀;2)修改位置x的字符为y;3)在S的第x个字符后添加一个字符
    2. 做法:splay每个节点维护size[i]:子树大小,hash[i]:以i为根节点的子树的hash值,(hash[fa]=hash[lson]+s[fa]*X^{size[lson]}+hash[rson]*X^{size[lson]+1}),对于查询,每次把一个后缀旋转到一颗子树上,子树的根节点的hash值就是答案,对于修改,直接修改从这个点到根的路径上的hash值,对于插入,直接splay插入更新size和hash。代码等到学splay专题的时候我再努力写吧。

    Problem D [BZOJ3555]

    1. 题意:n个长度为L的互不相同的字符串,求有多少对字符串互相相似。相似:如果两个字符串恰好有一个字符不同,则他们相似。n≤30000,L≤200,|字符集|≤64
    2. 做法:预处理每个串前缀的hash值,枚举哪一位不同,排序计算贡献。关键是如何快速求出去除第i位的字符串的hash值。下面进行推导:
      对于字符串S, (hash[i] = S[1]*X^{i-1} + S[2]*X^{i-2} + ... + S[i])
      除去S串的第(x)个字符的字符串(Hash = (S[1]*X^{n-1} + S[2]*X^{n-2} + ... + S[x-1]*X^{n-x+1})+(S[x+1]*X^{n-x}+S[x+2]*X^{n-x-1}...+S[n])\= hash[x-1]*X^{n-x+1} +(hash[n]-hash[x]*X^{n-x}))
    #include <bits/stdc++.h>
    #define rep(i,a,b) for(int i=a;i<=b;++i)
    #define frep(i,a,b) for(int i=a;i>=b;--i)
    typedef unsigned long long ll;
    const int N = 50100;
    const ll base = 107;
    using namespace std;
    int n,q,L,X;
    ll P[211], h[N][211],a[N];
    char s[211];
    ll cal(int s,int x) {
        return h[s][x-1]*P[L-x+1] +(h[s][L]-h[s][x]*P[L-x]);
    }
    int main() {
        scanf("%d%d%d",&n,&L,&X);
        P[0]=1;rep(i,1,L)P[i]=P[i-1]*base;
        rep(i,1,n) {
            scanf(" %s",s+1);
            rep(j,1,L) h[i][j]=h[i][j-1]*base+s[j];
        }
        int ans=0;
        rep(i,1,L){
            rep(j,1,n)a[j]=cal(j,i);
            sort(a+1,a+n+1);
            int tmp = 0;
            rep(j,2,n){
                if(a[j]!=a[j-1])tmp=0;
                else ++tmp;
                ans += tmp;
            }
        }
        printf("%d
    ",ans);
        return 0;
    }
    
    

    二维hash[BZOJ2462]

    1. 题意:给定一个M行N列的01矩阵,以及Q个A行B列的01矩阵,你需要求出这Q个矩阵哪些在
      原矩阵中出现过。所谓01矩阵,就是矩阵中所有元素不是0就是1。
    2. 做法:类似一维hash,先竖着hash,再横着hash,两次要用不同的base,查询子矩形方法类似于差分。
    3. hints:本题要对矩阵的hash值二次取mod,用map会tle
    #include <bits/stdc++.h>
    #define rep(i,a,b) for(int i=a;i<=b;++i)
    typedef unsigned long long ll;
    const int N = 1007;
    const ll base1 = 107;
    const ll base2 = 31;
    const ll mod = 10000007;
    using namespace std;
    int n,m,a,b,q;
    ll P1[N], P2[N], h[N][N];
    bool vis[mod+1];
    char mp[N][N];
    void init_hash() {
        P1[0]=1; rep(i,1,100) P1[i]=P1[i-1]*base1;
        P2[0]=1; rep(i,1,100) P2[i]=P2[i-1]*base2;
        rep(i,1,n)rep(j,1,m) h[i][j] += h[i-1][j]*base1 + mp[i][j];
        rep(i,1,n)rep(j,1,m) h[i][j] += h[i][j-1]*base2;
    }
    ll ask(int x,int y,int xl,int yl) {
        return h[x][y]-h[x-xl][y]*P1[xl]-h[x][y-yl]*P2[yl]+h[x-xl][y-yl]*P1[xl]*P2[yl];
    }
    int main() {
        scanf("%d%d%d%d",&n,&m,&a,&b);
        rep(i,1,n)scanf(" %s",mp[i]+1);
        init_hash();
        rep(i,a,n)rep(j,b,m) vis[ask(i,j,a,b)%mod]=1;
        scanf("%d",&q);
        while(q--) {
            rep(i,1,a)scanf(" %s",mp[i]+1);
            memset(h,0,sizeof(h));
            rep(i,1,a)rep(j,1,b)h[i][j]+=h[i-1][j]*base1+mp[i][j];
            rep(i,1,a)rep(j,1,b)h[i][j]+=h[i][j-1]*base2;
            printf("%d
    ",vis[h[a][b]%mod]);
        }
        return 0;
    }
    
    
  • 相关阅读:
    Hadoop压缩
    Hive数据倾斜总结
    DQL、DML、DDL、DCL
    HashMap/HashSet,hashCode,哈希表
    HashMap详解
    Hive分区和桶
    String不可变StringBuffer可变
    Java线程和守护进程
    Ambari安装小记
    P3
  • 原文地址:https://www.cnblogs.com/RRRR-wys/p/9256622.html
Copyright © 2020-2023  润新知