---
# System prepended metadata

title: KỸ THUẬT HASH BẰNG NGHỊCH ĐẢO MODULO

---

# KỸ THUẬT HASH BẰNG NGHỊCH ĐẢO MODULO

**1. Cài đặt hash [1, n]**

- Gọi powBase[i] là mũ base từ 0 đến n - 1 (n = s.size()).
```C++
const long long mod = 1e9 + 7;
const int base = 311; //Thường base là snt
//Tính powBase
powBase[0] = 1; //base^0 = 1
for(int i = 0; i < n; i++){
    powBase[i] = (1LL * powBase[i - 1] * base) % mod;
}

//Tính hash 1 -> n
hash[0] = s[0] - 'a' + 1; // '+1' lên để tránh TH s[0] - 'a' = 0 thì lúc đó sẽ gây cản trở việc tính hash sau này
for(int i = 1; i <= n; i++){
    hash[i] = hash[i - 1] + 1LL * (s[i] - 'a' + 1) * powBase[i]; 
}
```
**2. Lấy hash trong [L, R]**
- Ta có công thức như sau:
 
![image](https://hackmd.io/_uploads/SygW6Ebu1x.png)

- Cách chứng minh (bằng cách thế số):

    VD: Ta có dãy số dưới và cần tính đoạn [5, 6], thì ta có cách làm:
![image](https://hackmd.io/_uploads/H1yFT4-dJl.png)

Tuy nhiên, khi quay lại công thức trên, ta để ý rằng công thức không để dạng chia bởi vì không có modulo nào cho phép chia cả. Nhưng bài toán vẫn chưa dừng lại ở đây, ta cần phải tính (base^L^)^-1^ % p, và ta sẽ giải quyết vấn đề đó ở phần tiếp theo.

**3. Xử lí phần modulo**
- Theo định lý fermat nhỏ thì: a^p-1^ ≡ 1 (mod p), p là số nguyên tố và a không chia hết cho p (Phần chứng minh ở dưới cùng).
 
     Từ công thức trên ta biến đổi:
     ![image](https://hackmd.io/_uploads/ryoMx3-_1l.png)
     Vì p là hằng số rất lớn nên ta không cần lo p - 2 < 0.
     Từ đó, ta hãy coi (base^L^)^-1^ chính là a^-1^ và từ đó ta chỉ cần tính a^p-2^ là xong.
     - Code tính a^b^ trong O(log~2~b):
     
     ```C++
     long long ans;
     const long long p = 1e9 + 7;
     long long inversePow(long long a, long long b){
         if(b == 0) return 1; //a mu 0 = 1
         long long cur = inversePow(a, b / 2);
         cur = (cur % p * cur % p) % p;
         if(b % 2 != 0) cur = (cur * a) % p;
         return ans = cur;
     }
    ```
    - Và ta có code tìm hash [L, R] hoàn chỉnh như sau:
    
    ```C++
    #include <bits/stdc++.h>
    using namespace std;
    #define ll long long
    ll powBase[1000005], pre[100005], pre2[100005];
    string s, t;
    const long long p = 1e9 + 7;
    const int base = 311; // Base thường sẽ là snt, cụ thể  là 311 hay 31

    // Tính modulo đảo
    long long inversePow(long long a, long long b) {
        if (b == 0) return 1; // a mu 0 = 1
        long long cur = inversePow(a, b / 2);
        cur = (cur % p * cur % p) % p;
        if (b % 2 != 0) cur = (cur * a) % p;
        return cur;
    }

    // Tính hash L - R
    // Hash [L, R] xâu S
    long long getHashS(int L, int R) {
        return (pre[R] - pre[L - 1]) % p * inversePow(1LL * powBase[L], p - 2) % p;
    }

    // Hash [L, R] xâu T
    long long getHashT(int L, int R) {
        return (pre2[R] - pre2[L - 1]) % p * inversePow(1LL * powBase[L], p - 2) % p;
    }

    int main() {
        cin >> s >> t;
        int n = max(s.size(), t.size());
        s = ' ' + s;
        t = ' ' + t; // 1-based index

        // Tính base^i;
        powBase[0] = 1; // base^0 = 1
        for (int i = 1; i <= n; i++) {
            powBase[i] = (1LL * powBase[i - 1] * base) % p;
        }

        // Tính hash 1 -> n
        pre[0] = s[0] - 'a' + 1;
        pre2[0] = t[0] - 'a' + 1;

        // hash xâu s
        for (int i = 1; i < 1LL * s.size(); i++) {
            pre[i] = pre[i - 1] + 1LL * (s[i] - 'a' + 1) * powBase[i];
        }

        // hash xâu t
        for (int i = 1; i < 1LL * t.size(); i++) {
            pre2[i] = pre2[i - 1] + 1LL * (t[i] - 'a' + 1) * powBase[i];
        }

        // Lấy hash L - R
        cout << getHashS(4, 5) << " " << getHashT(2, 3);
    }

    
**4 Optimize code**
- Tuy nhiên, để optimize code trên thì ta dùng thêm mảng invBase[i] là modulo nghịch đảo của basePow[i]. Code từ O(Q * log~2~p) xuống còn O(Q + log~2~p) với Q là số truy vấn.
- Nhận xét rằng:
    
    inv[N] = base^-N^ . base^0^ 
    
    inv[N - 1] = base^-N^ . base^1^ = inv[N] * base
    
    inv[N - 2] = base^-N^ . base^2^ = inv[N - 1] * base
    
    inv[N - 3] = base^-N^ . base^3^ = int[N - 2] * base
    
    Qua đó ý tưởng sẽ là chạy vòng lặp i ∈ [n - 1, 0] để tính invBase[i]
- Code hoàn chỉnh (optimized):

```C++
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll powBase[1000005], pre[100005], pre2[100005], invPow[100005];
string s, t;
const long long p = 1e9 + 7;
const int base = 311; // Base thường sẽ là snt, cụ thể là 311 hay 31

// Tính modulo đảo
long long inversePow(long long a, long long b) {
    if (b == 0) return 1; // a mu 0 = 1
    long long cur = inversePow(a, b / 2);
    cur = (cur % p * cur % p) % p;
    if (b % 2 != 0) cur = (cur * a) % p;
    return cur;
}

// Tính hash L - R
// Hash [L, R] xâu S
long long getHashS(int L, int R) {
    return ((pre[R] - pre[L - 1]) * invPow[L]) % p;
}

// Hash [L, R] xâu T
long long getHashT(int L, int R) {
    return ((pre2[R] - pre2[L - 1]) * invPow[L]) % p;
}

int main() {
    cin >> s >> t;
    int n = max(t.size(), s.size());
    s = ' ' + s;
    t = ' ' + t; // 1-based index

    // Tính base^i
    powBase[0] = 1; // base^0 = 1
    for (int i = 1; i <= n; i++) {
        powBase[i] = (1LL * powBase[i - 1] * base) % p;
    }
    
    //Tính Inverse Base^i
    
    invPow[n] = inversePow(powBase[n], p - 2);
    for(int i = n - 1; i >= 0; i--) invPow[i] = (invPow[i + 1] * base) % p;

    // Tính hash 1 -> n
    pre[0] = s[0] - 'a' + 1;
    pre2[0] = t[0] - 'a' + 1;

    // hash xâu s
    for (int i = 1; i < 1LL * s.size(); i++) {
        pre[i] = pre[i - 1] + 1LL * (s[i] - 'a' + 1) * powBase[i];
    }

    // hash xâu t
    for (int i = 1; i < 1LL * t.size(); i++) {
        pre2[i] = pre2[i - 1] + 1LL * (t[i] - 'a' + 1) * powBase[i];
    }

    // Lấy hash L - R
    cout << getHashT(2, 3) << " " << getHashS(4, 5);
}

```
==Vì sao không chạy từ 1 đến n mà là n - 1 về 0:==

Quan sát được:

inv[0] = 1.

inv[1] = inv[0] : base = base^-1^ = inversePow(base, p - 2) = base^p-2^ mod p.

inv[2] = inv[1] : base.

inv[3] = inv[2] : base.

inv[4] = inv[3] : base.


Nhưng trong quá trình tính ta cần lấy modulo nữa nhưng modulo thì không dùng cho phép chia được nên ta bát bỏ ý tưởng trên.

    
:::info 
Chứng minh định lý Fermat nhỏ:
https://hackmd.io/@c3jv2tKJQeqHkvfbGE2CSA/chungminhdinhliFermatnho
:::


    

