Rolling-Hash

Rolling Hash

Created: 2023-05-23
Updated: 2024-04-30

概要

連続部分列の hash を O(1) で取得可能なデータ構造。

作成part

Hash関数

  • 基数 と 法 を定めて、法の下で 基数の累乗を線形結合的にしてhashを得る。
  • N ≦ 10^5 なら、基数を 2^61 - 1 にしてる為、hash衝突がほとんど起こらない。( sqrt(基数) くらいだと 50% 位衝突 )

法 と 基数

method

  • mulMod(x ,y) : 積の剰余を取得する関数。( __int128_t に対して % は遅い為作成する )
  • get(l,r) : インスタンス作成時の文字列の [l,r) の部分列の hash を求める関数。(累積和的に hash が求まる)

Program

using ull = unsigned long long;
vector<ull> powr; // 基数の累乗

class RollingHash {
    public:
    RollingHash(const string &s){
        if(base == 0){
            // base を 2^61-1の原始根 & 1e9以上 で初期化
            random_device rnd;
            mt19937 mt(rnd());
            uniform_int_distribution<ull> rand(1,INT_MAX);
            while(1){
                ull k = rand(mt);
                base = modPow(37,k);
                if(euclid(Mod-1,k) == 1 && base > 1e9)break;
            }
        }
        powr.assign(1,1);
        int size = s.size();
        hash.assign(size+1,0);
        // 左端~の hash 前計算
        for(int i=0; i<size; i++){
            hash[i+1] = mulMod(hash[i],base) + s[i];
            hash[i+1] = getMod(hash[i+1]);
        }
    }

    ull get(int l, int r){
        ull res = hash[r] - mulMod(hash[l],getPow(r-l));
        return getMod(res + Positivizer);
    }

    private:
    static const ull Mod = (1LL << 61) - 1;
    static const ull Positivizer = Mod * 7;
    static ull base;  // 基数
    vector<ull> hash; // hash の cash

    static ull getPow(int n){
        while(powr.size() <= n){
            // 次の基数の累乗を求める
            powr.push_back(mulMod(powr.back(), base));
        }
        return powr[n];
    }

    static ull mulMod(ull x,ull y){
        // Fermatの小定理 & 2進数のmod を利用
        __int128_t t = (__int128_t) x*y;
        t = (t >> 61) + (t & Mod);
        if(t >= Mod)t -= Mod;
        return (ull)t;
    }

    static ull getMod(ull val){
        val = (val >> 61) + (val & Mod);
        if(val >= Mod)val -= Mod;
        return (ull)val;
    }

    static ull euclid(ull a,ull b){
        if(b != 0)return euclid(b,a%b);
        else return a;
    }

    static ull modPow(ull a, ull b) {
        ll res= 1;
        for(; b; a=getMod(a*a), b>>=1) {
            if(b & 1)res = getMod(res*a);
        }
        return res;
    }
};
ull RollingHash::base;