【哈希】

【哈希】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计算索引
de771eff-d6c6-4091-a696-526f6f53d6e4

若key为字符串?->转为哈希值
7c02a439-ab44-4b63-acf8-4ba0d678eec6

双哈希
选取两个大质数a,b。当且仅当两个字符串的哈希值对a和对b取模都相等时,才认为这两个字符串相等

拉链法/开散列表
a9ab127d-805b-4898-a95d-83336cef1df3

//直接当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/

题目大意

04646119-009d-4176-b10a-8f213df33547

思路

找必要条件->一个不容易重复的特征点:(和+积)%质数

定义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;
}
posted @ 2025-08-26 10:08  White_ink  阅读(4)  评论(0)    收藏  举报