关于一些哈希
随缘更新,但考虑到马上要退役,毕业前应该没机会力。
求字符串的最长公共前缀
标准
空间复杂度:\((\sum_i |s_i|)\),但根据具体场景通常可以缩小至\(O(n)\)。
时间复杂度:\(O(\sum_i |s_i|)\)预处理,\(O(\log min(|s_i|,|s_j|))\)求两字符串的最长公共前缀
对于每个字符串,预处理其前缀hash数组。查询时二分找到最小的位置mid,令hash(i,mid)=hash(j,mid)。
使用这个技巧的题目,时间复杂度可能为\(O(n\log n)\)。
变形
以TJOI2017 DNA为例。
题目描述
加里敦大学的生物研究所,发现了决定人喜不喜欢吃藕的基因序列 \(S\),有这个序列的碱基序列就会表现出喜欢吃藕的性状,但是研究人员发现对碱基序列 \(S\),任意修改其中不超过 \(3\) 个碱基,依然能够表现出吃藕的性状。现在研究人员想知道这个基因在 DNA 链 \(S_0\) 上的位置。所以你需要统计在一个表现出吃藕性状的人的 DNA 序列 \(S_0\) 上,有多少个连续子串可能是该基因,即有多少个 \(S_0\) 的连续子串修改小于等于三个字母能够变成 \(S\)。
对于 \(100\%\) 的数据,\(S_0,S\) 的长度不超过 \(10^5\),\(0\lt T\leq 10\)。
做法
二分哈希,找到前三个需要修改的字符(即无法匹配之处),判断剩余部分是否可以匹配。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define lld unsigned long long
const lld base = 19991;
const int N = 1e5+5, maxn = 1e5;
lld h[N], h2[N], bw[N];
int n, m;
char s[N], s2[N];
int get(int l, int r){
return h[r]-h[l-1]*bw[r-l+1];
}
int get2(int l, int r){
return h2[r]-h2[l-1]*bw[r-l+1];
}
void init(){
scanf("%s%s", s+1, s2+1);
n = strlen(s+1), m = strlen(s2+1);
for(int i = 1; i <= n; i++) h[i] = h[i-1]*base+s[i];
for(int i = 1; i <= m; i++) h2[i] = h2[i-1]*base+s2[i];
}
int check(int L, int R){
int l = L, r = R;
while(l < r){
int mid = (l+r)>>1;
if(get(L, mid) == get2(m-(R-L), m-(R-mid))) l = mid+1;
else r = mid;
} return l;
}
void solve(){
init(); int ans = 0;
if(m > n){ puts("0"); return ;}
for(int i = 1; i+m-1 <= n; i++){
int l = i, r = i+m-1;
for(int j = 1; j < 4; j++){
l = check(l, r)+1;
if(l > r) break;
} if(l > r){ ans++; continue ;}
if(get(l, r) == get2(m-(r-l), m)){
ans++; continue ;
}
} printf("%d\n", ans);
}
int main(){
bw[0] = 1;
for(int i = 1; i <= maxn; i++) bw[i] = bw[i-1]*base;
int t; scanf("%d", &t);
while(t--) solve();
return 0;
}
取出整段哈希中的一个区间
标准
不知道怎么描述,看代码差不多就能理解吧!
#define lld unsigned long long
// 这是个不太好的习惯,最好用ull表示……
const lld base = 233, mod = 1e9+7; // 貌似自然溢出也很好
lld hash[N], bw[N]; // hash有撞函数的可能,实际写代码时建议换一个函数名
lld get(int l, int r){ // 取区间[l,r]的哈希值
return (hash[r]-hash[l-1]*bw[r-l+1]%mod+mod)%mod;
}
int main(){
bw[0] = 1, hash[0] = 0;
for(int i = 1; i <= n; i++){ // 预处理
hash[i] = (hash[i-1]*base%mod+s[i])%mod;
bw[i] = bw[i-1]*base%mod;
}
}
例题
[ARC172C] Election:差不多就是板子的意味,但是比起板子,可以更快地上手哈希,对(我这样的)新手很有帮助。
[JSOI2009] 电子字典:这题有更简洁的写法,可以不用取区间再合并,但推荐写一写,熟悉一些细节的处理。注意常数大小,最好用set
而非map
。
回文串的哈希
标准
回文串的哈希前缀数组和后缀数组相同,这是显然的。
运营
使两回文串拼接成回文串,则有:
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define lld unsigned long long
const int N = 2e6+5, maxn = 2e6;
const lld base = 233, mod = 1e9+7;
lld ans = 0, h[N], bw[N];
int n, len[N];
char s[N];
lld qpow(lld x, lld y){
lld ret = 1;
while(y){
if(y&1) ret = ret*x%mod;
x = x*x%mod; y >>= 1;
} return ret;
}
unordered_map<lld, int> cnt;
int main(){
scanf("%d", &n); bw[0] = 1;
for(int i = 1; i <= maxn; i++)
bw[i] = bw[i-1]*base%mod;
for(int i = 1; i <= n; i++){
scanf("%d%s", &len[i], s+1);
lld hsh = 0;
for(int j = 1; j <= len[i]; j++)
hsh = (hsh*base+s[j])%mod;
hsh = hsh*qpow(bw[len[i]]-1, mod-2)%mod;
h[i] = hsh, cnt[hsh]++;
} for(int i = 1; i <= n; i++)
ans += cnt[h[i]];
printf("%llu\n", ans);
return 0;
}
本文来自博客园,作者:_kilo-meteor,转载请注明原文链接:https://www.cnblogs.com/meteor2008/p/18553672