随缘更新,但考虑到马上要退役,毕业前应该没机会力。
求字符串的最长公共前缀
标准
空间复杂度:\((\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\)。
做法
二分哈希,找到前三个需要修改的字符(即无法匹配之处),判断剩余部分是否可以匹配。
取出整段哈希中的一个区间
标准
不知道怎么描述,看代码差不多就能理解吧!
#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
。