zoukankan      html  css  js  c++  java
  • 进制 /字符串 hash

    参考博客:

    哈希从入门到精通

    万能的进制哈希

    本文内容链接:

    1)基本概念

    2)解决hash冲突

    3)查询字串hash值

    4)删除后的hash值

    5)求回文串个数/位置

    6)hash与kmp

    7)线段树维护hash值

    基本概念

    进制hash:设置一个进制数 base,一个取模数mod,将字符串通过进制公式转化为数字(尽可能唯一

    进制hash公式

    (ans = (base*ans+s[i])\%mod)

    这个公式也就是进制转换的公式,例如十进制的 101

    第一次百位: ans = 10 * 0 + 1 = 1, 十位 :ans = 10 * 1 + 0 = 10,个位:ans = 10 * 10 +1 = 101;

    解决hash冲突

    自然溢出:

    通过 unsigned long long 会在超过 (2^{32}) 自动取模,对于一些长度长度较大的字符串,可能被卡常数

    双hash:

    通过取两个不同的mod(余数),来降低hash冲突,当然按理也可以取更多,不过时间上就着不住了

    查询字串hash值

    由于进制hash是将字符串转换为进制数,那么根据进制数的位数取得原理,自然也就能取得字串

    例如: 十进制:12345 , 要取得 234 ,就要利用 1234 - 1*1000 , 用 (p[i]) 表示(space base space)的第(space ispace)次方,

    在用(h[i])表示字符串的前缀hash值,这里指 十进制 的前(i)位值

    如果要取得 234 即从最低的第 2 位开始取 3 位,即用前(r)位的值减去前$ l-1(位左移)(r-l+1)$位

    得到公式(h[r]-h[l-1]*p[r-l+1])

    这里(r即为4,l即为2) ,所以得到 (1234 - 1*1000 = 234)

    typedef unsigned long long ull;
    ull get_Hash(int l, int r) {
        return h[r] - h[l - 1] * p[r - l + 1];
    }
    

    求删除字串字符后hash值

    现在有一个字符串s,每次询问在其某个区间([l,r])删除某段子字符串([L,R])后的字符串hash值

    思路:我们可以把字符串分为两段得到 ([l,L-1]与[R+1,r]),将这两段拼接就能得到结果hash值

    typedef unsigned long long ull;
    ull get_hash(int l, int r) {
        return h[r] - h[l - 1] * p[r - l + 1];
    }
    ull get_s(int l, int r, int L,int R) {
        return get_hash(l, L - 1) * p[r - R] + get_hash(R + 1, r);
    }
    

    求最长回文子串/回文子串数

    思路:通过枚举端点以及二分回文串长度,可以达到 (O(nlogn))

    例题:SP7586 NUMOFPAL - Number of Palindromes

    #include <bits/stdc++.h>
    #define IOS ios::sync_with_stdio(0); cin.tie(0);
    #define mp make_pair
    #define Pi acos(-1.0)
    #define accept 0
    typedef unsigned long long ull;
    using namespace std;
    ull base = 131;
    const int N = 10005;
    ull h[N],p[N],h1[N];
    ull ans;
    int len;
    char s[N];
    ull get_hash(int l,int r,int f){
    	if(f) {return h[r] - h[l-1]*p[r-l+1];}
    	else {return h1[l] - h1[r+1]*p[r-l+1];}
    }
    ull query(int pos,int f){
    	// 偶数 f = 1,奇数 f = 0;
    	int l = 1,r = min(pos,len-pos);
    	while(l<= r){
    		int mid = l+r>>1;
    		if(get_hash(pos - mid + f,pos + mid ,1) == get_hash(pos - mid + f,pos + mid,0)){
    			l = mid + 1;
    		}else r = mid - 1;
    	}
    	return r;
    }
    int main(){
    	scanf("%s",s+1);
    	len = strlen(s+1);
    	//预处理
    	p[0] = 1;
    	for(int i=1;i<=len;i++){
    		h[i] = h[i-1]*base + s[i];//正向hash
    		p[i] = p[i-1]*base;
    	//	h1[len-i+1] = h1[len-i+2]*base + s[len-i+1];//反向hash 
    	}
    	for(int i=len;i>=1;i--){
    		h1[i] = h1[i+1]*base + s[i];
    	}
    	ans = 0;
    	for(int i=1;i<len;i++){
    		ans += query(i,1) + query(i,0);
    	}
    	printf("%llu
    ",ans+len*1ull);
    }
    

    用hash代替kmp算法

    给出两个字符串s1和s2,其中s2为s1的子串,求s2在s1中出现多少次/出现的位置。

    具体做法是预处理出来两个串的hash值,因为求的是s2在s1中出现的次数,所以我们要匹配的长度被压缩到了s2的长度,所以我们只需要枚举s2在s1中的起点,看看后面一段长度为len的区间的hash值和s2的hash值一不一样就好。

    时间复杂度是(O(n+m))和kmp算法一样!

    #include <bits/stdc++.h>
    using namespace std;
    
    #define N 1000010
    #define ull unsigned long long
    #define base 131
    ull h[N], p[N], ha;
    char s1[N], s2[N];
    
    int main() {
        scanf("%s%s", s1 + 1, s2 + 1);
        int n = strlen(s1 + 1), m = strlen(s2 + 1);
        for (int i = 1; i <= m; ++i) ha = ha * base + (ull)s2[i];
        p[0] = 1;
        for (int i = 1; i <= n; ++i) {
            h[i] = h[i - 1] * base + (ull)s1[i];
            p[i] = p[i - 1] * base;
        }
        int l = 1, r = m, ans = 0;
        while (r <= n) {
            if (h[r] - h[l - 1] * p[m] == ha)
                ++ans;
            ++l, ++r;  ////同时右移
        }
        printf("%d
    ", ans);
    }
    

    线段树维护哈希

    例题:洛谷P2757

    #include<bits/stdc++.h>
    using namespace std;
    #define int unsigned long long 
    #define ls(p) (p<<1)
    #define rs(p) (p<<1|1)
    #define mid ((l+r)>>1)
    inline int read()
    {
        int x=0,f=1;
        char ch;
        for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
        if(ch=='-') f=0,ch=getchar();
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
        return f?x:-x;
    }
    int T,base=131;
    int a[100010],n;
    int ans1[500010],ans2[500010];
    int pw[100010];
    bool flag;
    inline void update(int tl,int tr,int l,int r,int p)
    {
        if(tl<=l&&r<=tr)
        {
            ans1[p]=ans2[p]=1;
            return;
        }
        if(tl<=mid)
            update(tl,tr,l,mid,ls(p));
        else
            update(tl,tr,mid+1,r,rs(p));
        ans1[p] = ans1[ls(p)] * pw[r-mid] + ans1[rs(p)] ;
        ans2[p] = ans2[rs(p)] * pw[mid-l+1] + ans2[ls(p)] ;
    }
    inline int query1(int tl,int tr,int l,int r,int p)
    {
        if(tl<=l&&r<=tr) return ans1[p];
        if(tr<=mid)    return query1(tl,tr,l,mid,ls(p));
        else if(mid<tl) return query1(tl,tr,mid+1,r,rs(p));
        else
        {
            int lx=query1(tl,tr,l,mid,ls(p));
            int rx=query1(tl,tr,mid+1,r,rs(p));
            return lx*pw[min(tr,r)-mid]+rx; 
        }
    }
    inline int query2(int tl,int tr,int l,int r,int p)
    {
        if(tl<=l&&r<=tr) return ans2[p];
        if(tr<=mid)    return query2(tl,tr,l,mid,ls(p));
        else if(mid<tl) return query2(tl,tr,mid+1,r,rs(p));
        else
        {
            int lx=query2(tl,tr,l,mid,ls(p));
            int rx=query2(tl,tr,mid+1,r,rs(p));
            return rx*pw[mid-max(tl,l)+1]+lx;
        }
    }
    signed main()
    {
        T=read();
        for(int i=pw[0]=1;i<=100000;++i)
            pw[i] = pw[i-1] * base;
        while(T--)
        {
            n=read();
            flag=0;
            memset(ans1,0,sizeof(ans1));
            memset(ans2,0,sizeof(ans2));
            for(int i=1;i<=n;++i)
            {
                a[i]=read();
                if(!flag)
                {
                    int d=min(a[i]-1,n-a[i]);
                    if(d)
                    {
                        if(query1(a[i]-d,a[i],1,n,1)^query2(a[i],a[i]+d,1,n,1))
                            flag=1;
                    }
                    update(a[i],a[i],1,n,1);
                }
            }
            puts(flag?"Y":"N");
        }
    return 0;
    }
    

    总结:hash

  • 相关阅读:
    SQLSERVER的非聚集索引结构
    SQLSERVER编译与重编译
    SQL Server读懂语句运行的统计信息 SET STATISTICS TIME IO PROFILE ON
    查看SQLSERVER内部数据页面的小插件Internals Viewer(续)
    关于学习编程和做好DBA的关系
    SQLSERVER中得到执行计划的方式
    SQLSERVER的排序问题
    对《30个提高Web程序执行效率的好经验》的理解
    挂载非引用Assembly中的事件
    枚举的多语言显示
  • 原文地址:https://www.cnblogs.com/Tianwell/p/12689859.html
Copyright © 2011-2022 走看看