Randomized hashes compress structured objects (strings, sets, multisets, positions) into fingerprints for comparison or dictionary lookup, and random test points verify identities that are expensive to compute. Hash equality is evidence, not proof: budget the error over all comparisons, and keep the randomness hidden from the adversary. Polynomial string hashing in depth is in Hashing.

CheckMethodError
two multisets equalsum of random 64-bit weights (mod ) per comparison
every value occurs an even number of timesXOR of random weights (Zobrist)
subarray is a permutation of sum hash against the hash of
for matricesFreivalds: test for random per round with a 0/1 vector, with a random vector mod
polynomial identically zero, two expressions equalevaluate at random points (Schwartz–Zippel)$\le \frac{d}{
a perfect matching existsrandom Tutte matrix, rank: General Matching
two rooted trees isomorphicrandom polynomial tree hashTree Isomorphism

Verifying is easier than computing: Freivalds checks a matrix product in instead of , and randomness closes the gap.

Set hashing and Zobrist hashing

Assign each value a random 64-bit weight; hash a multiset by the sum of the weights (mod ) and a set-with-parity by the XOR. Updates are , so prefix arrays give subarray hashes. Uses: comparing multisets, board positions in game engines (Zobrist hashing), tree hashing, “do these two ranges contain the same multiset”, counting subarrays where every value occurs an even number of times (pairs of equal prefix XORs), or a multiple of times (a base- digit hash).

struct SetHashing {                                      // values a[i] in [0, maxValue]
    vector<ull> weight, prefSum, prefXor, permSum;
    SetHashing(const vector<int>& a, int maxValue, ull seed) {
        mt19937_64 gen(seed); weight.resize(maxValue + 1);
        for (auto& w : weight) w = gen();
        int n = sz(a); prefSum.assign(n + 1, 0); prefXor.assign(n + 1, 0);
        for (int i = 0; i < n; i++) prefSum[i + 1] = prefSum[i] + weight[a[i]], prefXor[i + 1] = prefXor[i] ^ weight[a[i]];
        permSum.assign(maxValue + 2, 0);                 // permSum[k] = weight[1] + ... + weight[k]
        for (int k = 1; k <= maxValue; k++) permSum[k] = permSum[k - 1] + weight[k];
    }
    ull sumHash(int l, int r) const { return prefSum[r] - prefSum[l]; }   // multiset of a[l, r)
    ull xorHash(int l, int r) const { return prefXor[r] ^ prefXor[l]; }   // values with odd count
    bool isPermutation(int l, int r) const { int k = r - l; return k < sz(permSum) && sumHash(l, r) == permSum[k]; }
};
 
ll countEvenCountSubarrays(const SetHashing& h, int n) {   // subarrays where every value occurs an even number of times
    unordered_map<ull, int> seen; ll res = 0;
    for (int i = 0; i <= n; i++) res += seen[h.prefXor[i]]++;
    return res;
}
 
// Subarrays where every value occurs a multiple of k times: hash sum_v (count_v mod k) * w_v modulo 2^61 - 1.
ll countMultipleOfKSubarrays(const vector<int>& a, int k, int maxValue, ull seed) {
    const ull M = (1ULL << 61) - 1;
    auto mulmod = [&](ull x, ull y) { __uint128_t z = (__uint128_t)x * y; ull r = (ull)(z & M) + (ull)(z >> 61); return r >= M ? r - M : r; };
    mt19937_64 gen(seed); vector<ull> w(maxValue + 1); for (auto& x : w) x = gen() % M;
    vector<int> cnt(maxValue + 1, 0); ull H = 0; map<ull, int> seen; seen[0] = 1; ll res = 0;
    for (int v : a) {
        if (cnt[v] == k - 1) { H = (H + M - mulmod(w[v], k - 1)) % M; cnt[v] = 0; }   // digit k-1 -> 0
        else { H = (H + w[v]) % M; cnt[v]++; }
        res += seen[H]++;
    }
    return res;
}

Freivalds and Schwartz–Zippel

// Probabilistic check of A * B == C (n x n, entries mod p), O(rounds * n^2); false is always correct.
bool freivalds(const vector<vector<ll>>& A, const vector<vector<ll>>& B, const vector<vector<ll>>& C, ll p, int rounds, mt19937_64& rng) {
    int n = sz(A);
    auto mul = [&](const vector<vector<ll>>& M, const vector<ll>& x) {
        vector<ll> y(n, 0); for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) y[i] = (y[i] + (i128)M[i][j] * x[j]) % p; return y;
    };
    while (rounds--) {
        vector<ll> r(n); for (auto& x : r) x = rng() % p;
        if (mul(A, mul(B, r)) != mul(C, r)) return false;
    }
    return true;
}

Schwartz–Zippel. A non-zero polynomial of total degree over a field, evaluated at a point whose coordinates are independent uniform elements of a finite set , vanishes with probability at most . This one lemma underlies polynomial identity testing (evaluate both sides of a conjectured identity at random points mod a large prime), string and tree hashing, the Tutte matching test and Freivalds with random vectors.

Anti-hash attacks and defences

AttackBreaks
Thue–Morse strings of length every polynomial hash modulo (natural ull overflow), for every base
even base modulo only the last 64 characters matter:
birthday attacka fixed in offline work: trivial for
keys that are multiples of the bucket countunordered_map with the default hash<long long> (the identity on GCC)

Defences: a random base modulo the Mersenne prime (or two independent moduli), seeded from the clock; for hash maps the randomized splitmix64 SafeHash in C++ for CP (or gp_hash_table with it); store lengths when sequences of different sizes must be distinguished; avoid zero encodings (map characters to ).

Working rule. Find collisions in a tiny modulus deliberately to see the birthday bound in action; then quantify why a larger, independently randomized field changes the bound ( comparisons, error , plus when all pairs are compared).