Map each string to a number so that comparing substrings becomes . Simple, general, and the fastest route to a working solution for most string problems — provided you defend against anti-hash tests.

Polynomial hashing

Precompute prefix hashes and powers, then any substring hash is :

or, avoiding the inverse, compare against .

struct Hashing {
    static const long long M = (1LL << 61) - 1;      // Mersenne prime
    long long B;
    vector<long long> h, p;
 
    static long long mul(long long a, long long b) {  // mod 2^61-1 without __int128 division
        __uint128_t r = (__uint128_t)a * b;
        long long lo = (long long)(r & M), hi = (long long)(r >> 61);
        long long s = lo + hi;
        return s >= M ? s - M : s;
    }
    Hashing(const string& s, long long base) : B(base), h(s.size()+1, 0), p(s.size()+1, 1) {
        for (size_t i = 0; i < s.size(); i++) {
            h[i+1] = (mul(h[i], B) + s[i]) % M;
            p[i+1] = mul(p[i], B);
        }
    }
    long long get(int l, int r) const {               // inclusive [l, r]
        long long res = (h[r+1] - mul(h[l], p[r-l+1])) % M;
        return res < 0 ? res + M : res;
    }
};

Note this version builds the hash left to right so that get needs no inverse — the cleaner formulation.

Defending against collisions

Anti-hash tests are real

Codeforces problems routinely include tests built to break M = 10^9+7 with a fixed base. The Thue-Morse sequence breaks any base modulo in length; birthday attacks find collisions for a fixed pair in tries.

Four defences, in order of effectiveness:

  1. Randomise the base at runtime. B = uniform_int_distribution<long long>(256, M-2)(rng) with a time-seeded RNG. This alone defeats every precomputed test.
  2. Use . A birthday attack needs candidates, which is out of reach inside a submission.
  3. Double hashing. Two independent pairs, compared as a pair. Effectively .
  4. Never use unsigned long long overflow () — it is broken by Thue-Morse strings regardless of the base.

With a random base modulo , the collision probability over comparisons is about — negligible.

What hashing buys you

TaskMethod
Substring equalitycompare hashes,
Compare substrings lexicographicallybinary search the LCP, then compare one character —
Pattern matchingrolling hash over the text
Count distinct substrings of length hash every window, count distinct
Longest common substring of two stringsbinary search the length + hash sets,
Longest palindromic substringbinary search with forward and reverse hashes
Is a palindrome?compare forward and reverse hashes,
Longest common prefix of two suffixesbinary search on the hash
Match a pattern with wildcardshashing plus FFT, or per-position checks
Tree hashingthe same idea on tree structures

Palindrome checking in

Keep a second hash of the reversed string. Then is a palindrome iff its forward hash equals the corresponding reverse-string hash — a two-line addition that replaces Manacher for many problems.

Hashing vs the deterministic alternatives

HashingSuffix automaton / array
Code length~30 lines40-90 lines
Correctnessprobabilisticexact
Substring compare after build
Count distinct substrings
Memory to
Anti-test riskreal, mitigablenone
Generalityvery highhigh

Hashing is the right first tool. Switch to a suffix automaton when you need to enumerate or count over all substrings, which hashing cannot do efficiently.

Hashing beyond strings

The same polynomial trick hashes any sequence: arrays of integers, tree shapes, multisets (sum of over elements, with a random scramble), and 2D grids (hash rows, then hash the row hashes).

See also: Rolling Hash · Rabin-Karp · Suffix Automaton