哈希
https://zhuanlan.zhihu.com/p/104346215
本命素数:\(\mathtt{202099}\);\(\mathtt{1145142007}\);\(\mathtt{11451419198101979}\)。
1 生日悖论
在认为完全随机的情况下,我们需要会分析冲突的概率。
我们需要给 \(n\) 个位置每一个位置分配一个 \([1, V]\) 的数,使得没有两个数相等。生日悖论的内容是,这样做的成功率是:\(\cfrac{V}{V} \times \cfrac{V-1}{V} \times ... \times \cfrac{V-n}{V}\)。
因为 \(n = 3 \times 10^5\),如果 \(V = 10^9\),那么几乎不可能成功。因此设置 \(V = 10^{18}\) 是必要的。
2 一些好的哈希方式
令哈希方式有强随机性是很重要的。
2.1 字符串哈希
千万千万千万不能自然溢出,一定会被卡。其他的符合生日悖论的概率就好了。
允许 \(k\) 次失配的字符串匹配
问题:给定长为 \(n\) 的源串 \(s\),以及长度为 \(m\) 的模式串 \(p\),要求查找源串中有多少子串与模式串匹配。\(s'\) 与 \(s\) 匹配,当且仅当 \(s'\) 与 \(s\) 长度相同,且最多有 \(k\) 个位置字符不同。其中 \(1 \le n,m \le 10^6\),\(0 \le k \le 5\)。
这道题无法使用 KMP 解决,但是可以通过哈希 + 二分来解决。
枚举所有可能匹配的子串,假设现在枚举的子串为 \(s'\),通过哈希 + 二分可以快速找到 \(s'\) 与 \(p\) 第一个不同的位置。之后将 \(s'\) 与 \(p\) 在这个失配位置及之前的部分删除掉,继续查找下一个失配位置。这样的过程最多发生 \(k\) 次。
总的时间复杂度为 \(O(m+kn \log_2 m)\)。
2.2 sum hash 和 xor hash
这些 hash 的目的其实就是把某一些观测的现象(是否满足某个特定结构)用一个数字表示,满足这些现象到达一些指定状态的时候数字是我们可以预见的,方便维护。
一些方法:我们如果要求某一个东西出现偶数次,那么出现一次的时候异或上 \(x\)。
对于一个颜色序列,我们维护双指针 \([l, r]\),想要维护序列中是否满足:每一种颜色要么不在 \([l,r]\) 中出现,要么在所有出现位置都在 \([l, r]\) 中。我们可以考虑对前面的出现位置赋随机权值,然后令最后一个位置的权值为之前权值之和的相反数。然后维护 sum 即可。
CSP2022 T3
快速维护状态是否为指定状态,可以给每个元素指定随机哈希值,然后加和。
CF1771F
【题意】给定序列 \(a\),\(m\) 次询问 \([l,r]\) 求出区间内最小的出现奇数次的数。
【分析】考虑异或处理奇数和偶数次,随机给每个数赋值一个数,(mt19937_64是64位整数生成器)然后每次询问的冲突概率为 \(1/2^{64}\)。主席树上二分。
2.3 树哈希
https://peehs-moorhsum.blog.uoj.ac/blog/7891
考虑这样一种哈希方式。对于一棵以 \(a\) 为根的子树,假设儿子是 \(v_1,v_2,⋯,v_k\),定义子树的哈希 \(h(a)=1+∑ \limits_{1≤i≤k}f(h(v_i))\)。其中\(h(v_i)\) 是 \(v_i\) 对应子树的哈希,\(f\) 为一个待定函数。
可以证明:如果 \(f\) 为随机函数,这样的哈希在自然溢出下的期望冲突数不超过 \(O(n^2/2^w)\)。只需考虑最深的一对冲突点即可。
上述哈希最大的优势是好写。如果需要换根,第二次 dp 时只需把子树哈希减掉即可。(也就是随机 \(f\),再加起来加 \(1\))
实践中,我们并不能取一个真正的随机函数当 \(f\)。但事实上,没有特殊性质的 \(f\) 几乎都卡不掉;因为随便找个 \(f\) 大概率很随机。
注:可以把 \(f\) 看成一个 mt19937,但是这个比较慢,手写一个随机函数会比较好。
upd: 有一种 f 不会被卡掉,是 xorshift。
认准唯一真写法。
\(h(i)=\sum \limits_{j \in son_i} f(h(j)) + c\)
\(f(x)\) 使用 xorshift 的方法生成:
ull func(ull x) {
x^=(x<<7); x^=(x>>11); x^=(x<<13);
return x;
}