进制 /字符串 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 i\space\)次方,

在用\(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\n",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\n", 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

posted @ 2020-04-13 10:42  Tianwell  阅读(263)  评论(0编辑  收藏  举报