【哈希】
【哈希】Hash
模版代码
字符串哈希
//双哈希
const int N = 1 << 21;
static const i64 mod1 = 1E9 + 7, base1 = 127LL;
static const i64 mod2 = 1E9 + 9, base2 = 131LL;
using U = ModInt<i64,mod1>;
using V = ModInt<i64,mod1>;
vector<U> val1;
vector<V> val2;
void init(int n = N) {
val1.resize(n + 1), val2.resize(n + 2);
val1[0] = 1, val2[0] = 1;
for (int i = 1; i <= n; i++) {
val1[i] = val1[i - 1] * base1;
val2[i] = val2[i - 1] * base2;
}
}
struct String {
vector<U> hash1;
vector<V> hash2;
string s;
String(string s_) : s(s_), hash1{1}, hash2{1} {
for (auto it : s) {
hash1.push_back(hash1.back() * base1 + it);
hash2.push_back(hash2.back() * base2 + it);
}
}
pair<U, V> get() { // 输出整串的哈希值
return {hash1.back(), hash2.back()};
}
pair<U, V> substring(int l, int r) { // 输出子串的哈希值
if (l > r) swap(l, r);
U ans1 = hash1[r + 1] - hash1[l] * val1[r - l + 1];
V ans2 = hash2[r + 1] - hash2[l] * val2[r - l + 1];
return {ans1, ans2};
}
pair<U, V> modify(int idx, char x) { // 修改 idx 位为 x
int n = s.size() - 1;
U ans1 = hash1.back() + val1[n - idx] * (x - s[idx]);
V ans2 = hash2.back() + val2[n - idx] * (x - s[idx]);
return {ans1, ans2};
}
};
模版题
https://www.acwing.com/problem/content/139/
基本概念
一个东西出现了多少次/一个东西有没有重复出现
※常见场景:
(1)比较一次开销很大:求字符串判重、判断两个区间排序后是否完全相同
(2)比较一次开销不大,但是空间开不下
散列表,[key-value]键值对,unordered_map
哈希函数:为key计算索引
若key为字符串?->转为哈希值
双哈希
选取两个大质数a,b。当且仅当两个字符串的哈希值对a和对b取模都相等时,才认为这两个字符串相等
拉链法/开散列表
//直接当map用:[i64,int]
//注意当值没有被插入过:默认-1
struct hash_map { // 哈希表模板
struct data {
long long u;
int v, nex;
}; // 前向星结构
data e[SZ << 1]; // SZ 是 const int 表示大小
int h[SZ], cnt;
int hash(long long u) { return (u % SZ + SZ) % SZ; }
// 这里使用 (u % SZ + SZ) % SZ 而非 u % SZ 的原因是
// C++ 中的 % 运算无法将负数转为正数
int& operator[](long long u) {
int hu = hash(u); // 获取头指针
for (int i = h[hu]; i; i = e[i].nex)
if (e[i].u == u) return e[i].v;
return e[++cnt] = data{u, -1, h[hu]}, h[hu] = cnt, e[cnt].v;
}
hash_map() {
cnt = 0;
memset(h, 0, sizeof(h));
}
};
开放寻址法:解决哈希冲突
当插入键值对时,若计算出的哈希地址已被占用(发生冲突),则在哈希表的数组内部通过某种规则探查其他空位置
线性探查法、二次探查法、双重哈希
//线性探查法
constexpr int N = 360007; // N 是最大可以存储的元素数量
class Hash {
private:
int keys[N];
int values[N];
public:
Hash() { memset(values, 0, sizeof(values)); }
int& operator[](int n) {
// 返回一个指向对应 Hash[Key] 的引用
// 修改成不为 0 的值 0 时候视为空
int idx = (n % N + N) % N, cnt = 1;
while (keys[idx] != n && values[idx] != 0) {
idx = (idx + cnt * cnt) % N;
cnt += 1;
}
keys[idx] = n;
return values[idx];
}
};
【例题】
[进阶指南P60]雪花
https://www.acwing.com/problem/content/139/
题目大意
思路
找必要条件->一个不容易重复的特征点:(和+积)%质数
定义Hash函数\(H(a_{i,1}, a_{i,2}, \dots, a_{i,6}) = \left( \sum_{j=1}^6 a_{i,j} + \prod_{j=1}^6 a_{i,j} \right) \bmod P\) ,判断是否相同即可
注意取P为最接近N的质数
代码
const int N=1e5+10;
//自己选的质数
const int P=99991;//P要略微小于N一点
int n,tot;
int snow[N][6],h[N],ne[N];//链式前向星插入
/*
判断雪花是否相等:hash特征点相同
->找不容易被叉掉的特征点:(和+积)的哈希值
*/
//求雪花哈希值
int H(int *a){
int sum=0,mul=1;
for(int i=0;i<6;i++){
sum=(sum+a[i])%P;
mul=(i64)mul*a[i]%P;
}
return (sum+mul)%P;
}
//判断两片雪花是否相同
bool equal(int *a,int *b){
for(int i=0;i<6;i++){
for(int j=0;j<6;j++){
bool eq=1;
for(int k=0;k<6;k++){
if(a[(i+k)%6]!=b[(j+k)%6]) eq=0;
}
if(eq) return true;
eq=1;
for(int k=0;k<6;k++){
if(a[(i+k)%6]!=b[(j-k+6)%6]) eq=0;
}
if(eq) return true;
}
}
return false;
}
bool insert(int *a){
int val=H(a);
//拉链法:在链上寻找形状相同的雪花
for(int i=h[val];i;i=ne[i]){
if(equal(snow[i],a)) return true;
}
//没找到就插入
tot++;
memcpy(snow[tot],a,6*sizeof(int));
ne[tot]=h[val];
h[val]=tot;
return 0;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++){
int a[6];
for(int j=0;j<6;j++) cin>>a[j];
if(insert(a)){
cout<<"Twin snowflakes found."<<endl;
return;
}
}
cout<<"No two snowflakes are alike."<<endl;
}
【题目积累】
Famous Choreographer
https://codeforces.com/contest/2132/problem/G
对位置哈希 字符做权重
二维前缀和维护哈希表
添加/还原用乘法 累计用加法
题目大意
希望这个字符阵列经过180度旋转后还能相同,问最少添加几个字符
思路
(1)同时在上下/左右添加是没用的,选一边加即可
(2)对称中心一定会在图形内
(3)选中不用添加的部分一定包含一个角:在中间没用,除非本来就是答案为0
->找翻转前后没变的子矩阵->哈希
代码
void solve(){
cin>>n>>m;
vector<string> s(n);
for(int i=0;i<n;i++) cin>>s[i];
//翻转180
vector<string> t=s;
for(int i=0;i<n;i++){
reverse(t[i].begin(),t[i].end());
}
reverse(t.begin(),t.end());
//预处理哈希:模乘->对位置进行哈希
vector<mint> pm(n*m+1,0);//0-based
pm[0]=1;
for(int i=1;i<=n*m;i++){
pm[i]=pm[i-1]*mod;
}
//二维前缀和维护哈希
vector<vector<mint>> hs(n+1,vector<mint>(m+1));
vector<vector<mint>> ht(n+1,vector<mint>(m+1));
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
hs[i+1][j+1]=hs[i+1][j]+hs[i][j+1]-hs[i][j]+s[i][j]*pm[i*m+j];
//给矩阵中每个位置的字符分配唯一的权重:不同位置相同字符哈希值不同
ht[i+1][j+1]=ht[i+1][j]+ht[i][j+1]-ht[i][j]+t[i][j]*pm[i*m+j];
}
}
auto get=[&](vector<vector<mint>> &h,int lx,int ly,int rx,int ry)->mint{
if(lx>rx) swap(lx,rx);
if(ly>ry) swap(ly,ry);
mint res=h[rx+1][ry+1]-h[lx][ry+1]-h[rx+1][ly]+h[lx][ly];
//对齐基准为右下角
res*=pm[n*m-rx*m-ry];
return res;
};
int ans=4*n*m; //复制3遍一定是答案
//枚举x y长度
for(int x=1;x<=n;x++){
for(int y=1;y<=m;y++){
int res=(2*n-x)*(2*m-y)-n*m;
if(get(hs,0,0,x-1,y-1)==get(ht,n-1,m-1,n-x,m-y)){
ans=min(ans,res);
}
if(get(hs,n-1,0,n-x,y-1)==get(ht,0,m-1,x-1,m-y)){
ans=min(ans,res);
}
if(get(hs,0,m-1,x-1,m-y)==get(ht,n-1,0,n-x,y-1)){
ans=min(ans,res);
}
if(get(hs,n-1,m-1,n-x,m-y)==get(ht,0,0,x-1,y-1)){
ans=min(ans,res);
}
}
}
cout<<ans<<endl;
}