字符串哈希:高效判等与碰撞分析
多项式哈希 双哈希策略 碰撞概率分析
字符串哈希将任意长度的字符串映射为固定范围的整数,通过 的数值比较替代 的字符比较,是处理字符串判等、重复性检测的有力武器。
1. 多项式哈希 (Polynomial Rolling Hash)
1.1 形式化定义与一致性分析
对于字符串 ,其多项式哈希定义为:
一致性保证:
- 可加性:。
- 子串提取:通过预处理前缀哈希 ,任何子串 的哈希值均满足:
这种一致性使得哈希可以无缝对接二分查找等算法,用于求最长公共前缀等问题。
2. 碰撞概率分析 (Collision Analysis)
2.1 理论碰撞概率
定理:对于模数为 的哈希函数,若进行 次比对,发生至少一次碰撞的概率 。
- 单哈希 ():当 时,。这意味着单哈希在 规模的随机数据下极易碰撞。
- 双哈希 ():当 时,。安全性极高。
2.2 防御 Anti-hash:随机化基数
攻击者可以通过构造特殊的“Thue-Morse 序列”等方式使得特定 下的哈希产生碰撞。
防御方案:在程序运行时,利用 std::chrono 或随机数引擎选择一个随机基数 。
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
const ull base = uniform_int_distribution<ull>(131, 10007)(rng);
3. 算法实现
🎯 综合练习
练习 1:[Luogu P3370] 字符串哈希模板
题目:求 个字符串中不同字符串的个数。
Check Solution
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <set>
using namespace std;
typedef unsigned long long ull;
const ull B = 131;
ull get_hash(string s) {
ull res = 0;
for (char c : s) res = res * B + c;
return res;
}
int main() {
int n; cin >> n;
vector<ull> h;
for (int i = 0; i < n; i++) {
string s; cin >> s;
h.push_back(get_hash(s));
}
sort(h.begin(), h.end());
cout << unique(h.begin(), h.end()) - h.begin() << endl;
return 0;
}
练习 2:[Luogu P3501] [POI2010] ANT-Antisymmetry
题目:定义两个字符串“反对称”为:一个串取反(0变1, 1变0)并翻转后与原串相同。求给定 01 串中所有反对称子串的总数。
Check Analysis
二分 + 哈希:
- 反对称子串的长度必然为偶数。
- 对于每个中心( 和 之间),其最长反对称半径具有单调性。
- 利用哈希判断 取反翻转后是否等于 。
- 二分半径长度,求出每个中心的最大半径并累加。
练习 3:[CF 514C] Watto and Mechanism
题目:查询一个字符串是否可以通过恰好修改一个字符,变成模式串集合中的某一个。
Check Solution
解法:预处理所有模式串的哈希值存入 set。查询时,对查询串枚举修改的每个位置和修改后的每个字符(共 种可能),利用 哈希计算修改后的值并在 set 中查询。
// 核心:修改第 i 位字符由 c1 变为 c2 后的新哈希值
ull new_hash = (old_hash - (ull)c1 * p[len - 1 - i] + (ull)c2 * p[len - 1 - i]);