参考博客:
本文内容链接:
基本概念
进制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