毒瘤养成记1: 如何卡hash

各位毒瘤大家好, 最近模拟赛考了一道trie+主席树好题, 但大家都用hash水过了这道题(包括我), 为了测试一下新搭建的HEAT OJ的hack功能, 我将继续扮演毒瘤的角色, 用毒瘤的艺术形象努力创造一个正能量的形象, 文体两开花, 弘扬中华文化, 右转去BZOJ搞了一晚上hashkiller, 回来卡了单哈希(双哈希是真滴卡不住

哈希(hash) :

利用大质数或其他对应函数把字符串转为一个正整数来快速判断字符串相等

通常可以模一个大质数或使用自然溢出

实现(例);

const int P = 1e9+7;
const int di = 1331;
hash[i] = (hash[i-1] * base + s[i]) % P;

其中\(base\), 我称之为底数, P我称之为模数, 事实上自然溢出相当于模了\(2^{64}\)

卡哈希的思想:

  1. 数学构造
  2. 随机数据(依据生日悖论

Part 1 生日悖论:

如果一个班级有23个人, 那么其中有两个人生日相同的概率超过50%

surprise 这与大部分人的直觉相违背, 所以称之为生日悖论

为什么会这样呢, 是自己的直觉不靠谱吗?

不, 我们可以考虑另一个问题, 如果一个班里有23人包括自己, 有人生日和自己相同的概率是多少?

没错, 大概为\(6%\)左右, 这是与直觉近似的, 其实我们的直觉正是把"有人生日相同"和"有人生日和自己相同"的概念相混, 实际有人生日和自己相同的概率确实很小

证明可以用排列组合开心的手玩一下

性质:

样本容量为\(n\), 有两个样本相同的概率为

\[1.18\sqrt{n} \]

Part 2 卡大质数hash (1000000009) :

考虑生日攻击, 随机一个1e5大小的字符串, 询问长度为\(L\)的本质不同子串有多少个, 用大质数\(hash\)和后缀数组(也可以用自然溢出\(hash\))对拍, 输出不同子串的终止位置, 拿\(fc\)命令对比一下, 找出\(hash\)值相等的不同子串

正确性如生日悖论, 大概有超过\(50%\)的几率成功, 实际上质数不强的时候有很多相同

Part 3 卡自然溢出hash:

自然溢出\(hash\)在数据随机的情况下正确性极高, 因为它的值域很大, 很难生日攻击

考虑特殊构造:

对于底数为偶数:

构造\(aaaa\cdots aaaa\)\(baaa\cdots aaaa\)两个长度相等且长度大于64的串

底数的六十四次方以上模\(P\)就会为零, \(b\)\(a\)也会被判为相等

对于底数为奇数:

不太好卡, 要用神仙的构造方法:

设一个串\(s[]\), \(s[1] = 'a'\) 设$ |s| = strlen(s + 1)$ 为\(s\)的长度

定义$ (!s)$ 为\(s\)中的字符全部\('a'变'b', 'b'变'a',\) 当然\(s\)中只含有\('a'\)\('b'\)两种字符

定义串\(S1 + S2\)\(S1\)串在前\(S2\)串在后拼接起来, \(hash(s1)\)\(s1\)的哈希值

类似数列的, 我们定义一个"字符串列", 为一个字符串集合{\(S_n\)}, 后一个字符串可以通过前一个字符串推出

\(S_1 = "a"\)

\[S_i = S_{i-1} + (!S_{i-1}) \]

\(S_i\)的长度为\(2^{i-1}\)

\[hash(S_i) = hash(S_{i-1}) * base^{|S_{i-1}|} + hash((!S_{i-1})) \\ = hash(S_{i-1}) * base^{2^{i-2}} + hash((!S_{i-1})) \\ hash((!S_{i-1})) = hash((!S_{i-2})) * base^{2^{i-2}} + hash(S_{i-1}) \\ hash(S_i) - hash((!S_{i-1})) = (hash(S_{i-1}) - hash((!S_{i-2}))) * base^{2^{i-2}} - (hash(S_{i-1}) - hash((!S_{i-2})))\\ hash(S_i) - hash((!S_{i-1})) = (hash(S_{i-1}) - hash((!S_{i-2}))) * (base^{2^{i-2}} - 1) \]

希望得到 \(2^{64} | hash(S_i) - hash(!S_i)\)\(g_i = hash(S_i) - hash(!S_i)\)

\(g_i=g_{i-1}*(base^{2^{i-2}}-1)\) 每个 \((base^{2^{i-2}}-1)\) 都是偶数, 这使得g到第64项就就可以卡掉hash了,

但事实上12位以上就行, 因为

\[base^{2^{i-1}}-1=(base^{2^{i-2}}-1)*(base^{2^{i-2}}+1) \]

为一个偶数乘一个偶数, 而左边的可以继续递归下去, 所以原式整除\(2^i\) 然后就结束啦

长大后, 我要当毒瘤, 爷爷奶奶可高兴了, 给我爱吃的...


posted @ 2020-01-20 20:17  Hs-black  阅读(4580)  评论(2编辑  收藏  举报