Hashing

Правка en3, от theoneandonlytron, 2026-10-13 03:29:28

This blog compiles what I have learned and my insights into solving problems. Some people might benefit from it, so enjoy!

Credit to USACO Guide for the problem list. Thank you so much!

Theory

  • String hashing converts a string into a number by treating its characters like digits in a chosen base. For example,

Unable to parse markup [type=CF_MATHJAX]

$. - We can map

Unable to parse markup [type=CF_MATHJAX]

$ to

Unable to parse markup [type=CF_MATHJAX]

$ and build a number using a base

Unable to parse markup [type=CF_MATHJAX]

$. - We take the result modulo

Unable to parse markup [type=CF_MATHJAX]

$ to keep the number small. However, this creates the possibility of a collision: two different strings can have the same hash modulo

Unable to parse markup [type=CF_MATHJAX]

$.

Template

const ll Mod = 1000000009LL;
const ll Base = 271LL;

struct hashed_string{
    private:
    string s; ll n;
    vector <ll> prefix;
    vector <ll> pow;
    public:
    hashed_string(string a){
        s = a; n = a.size();
        prefix.assign(n, 0);
        ll sum = 0;
        pow.assign(n+1, 0);
        pow[0] = 1;
        for (int i = 1; i <= n; i++){
            pow[i] = (pow[i-1] * Base) % Mod;
        }
        for (int i = 0; i < n; i++){
            prefix[i] = ((sum * Base) % Mod + s[i] - 'a' + 1) % Mod;
            sum = prefix[i];
        }
    }
    ll get_hash(ll l, ll r){
        ll x = prefix[r];
        if (l >= 1){
            l--;
            x = ((x - prefix[l] * pow[r - l] % Mod) % Mod + Mod) % Mod;
        }
        return x;
    }
};

Problem List

Problem 1

Problem

  • This problem uses template hashing. The number of comparisons is approximately

Unable to parse markup [type=CF_MATHJAX]

$. - For each candidate period length

Unable to parse markup [type=CF_MATHJAX]

$, compare every complete segment

Unable to parse markup [type=CF_MATHJAX]

$ with

Unable to parse markup [type=CF_MATHJAX]

$. - If the final segment is shorter than

Unable to parse markup [type=CF_MATHJAX]

$, compare it with the prefix of the same length.

Code

Problem 2

Problem

  • This problem is interesting because deleting a phrase might create a new censored phrase.
  • If we delete a segment

Unable to parse markup [type=CF_MATHJAX]

$, the remaining parts

Unable to parse markup [type=CF_MATHJAX]

$ and

Unable to parse markup [type=CF_MATHJAX]

$ join together. A new censored occurrence can cross this join. - This suggests changing the template from taking a complete string during initialization to starting with an empty string and allowing characters to be added one by one. - Add characters one by one. Whenever the current suffix equals the censored phrase, delete that suffix and continue. Maintain the prefix hashes alongside the characters so that deletions restore the previous state.

Code

Problem 3

Problem

  • This problem combines template hashing with an optimization trick.
  • Let

Unable to parse markup [type=CF_MATHJAX]

$ and

Unable to parse markup [type=CF_MATHJAX]

$ be the counts of

Unable to parse markup [type=CF_MATHJAX]

$ and

Unable to parse markup [type=CF_MATHJAX]

$ in

Unable to parse markup [type=CF_MATHJAX]

$. If the replacement lengths are

Unable to parse markup [type=CF_MATHJAX]

$ and

Unable to parse markup [type=CF_MATHJAX]

$, then

Unable to parse markup [type=CF_MATHJAX]

$. - Enumerate the replacement length for the more frequent symbol. Since

Unable to parse markup [type=CF_MATHJAX]

$, there are at most

Unable to parse markup [type=CF_MATHJAX]

$ candidate lengths. - For each candidate, calculate the other length and check the pattern using hashing in

Unable to parse markup [type=CF_MATHJAX]

$. This gives

Unable to parse markup [type=CF_MATHJAX]

$ time after hash preprocessing. - Both replacement strings must be nonempty, and they must be different.

Code

Problem 4

Problem

  • Some people use hashing and consider all permutations, but I use bitmask DP; it feels simpler to me.
  • First, remove any string that is a substring of another remaining string. Keep only one copy of duplicate strings.
  • For every pair

Unable to parse markup [type=CF_MATHJAX]

$, calculate the maximum overlap between a suffix of string

Unable to parse markup [type=CF_MATHJAX]

$ and a prefix of string

Unable to parse markup [type=CF_MATHJAX]

$. - Then use bitmask DP. Let

Unable to parse markup [type=CF_MATHJAX]

$ represent the minimum length after combining the strings in

Unable to parse markup [type=CF_MATHJAX]

$ and ending with string

Unable to parse markup [type=CF_MATHJAX]

$. - We only need to remember the last string because we have already removed strings contained in other strings.

Code

Problem 5

Problem

  • Binary search the substring length and use an

Unable to parse markup [type=CF_MATHJAX]

$ check, giving a total complexity of

Unable to parse markup [type=CF_MATHJAX]

$. - The check uses template hashing to compare the substrings.

Code

Problem 6

Problem

  • This problem uses simulation from each starting position. Hash the turn information together with the distances, then calculate the extra distance needed.
  • We need to distinguish straight movement, left turns, and right turns. In terms of directed turning angles, we can encode

Unable to parse markup [type=CF_MATHJAX]

$,

Unable to parse markup [type=CF_MATHJAX]

$, and

Unable to parse markup [type=CF_MATHJAX]

$ as three different values. - I used vectors to handle the turns. There is probably a cleaner implementation.

Code

Problem 7

Problem

  • This problem is really interesting, and the key observation is hard to find.
  • If we brute force every pair of strings

Unable to parse markup [type=CF_MATHJAX]

$ and calculate their Hamming distance, the complexity is

Unable to parse markup [type=CF_MATHJAX]

$. This is too slow when

Unable to parse markup [type=CF_MATHJAX]

$ is large. - What if we choose the approach based on which dimension is smaller? If

Unable to parse markup [type=CF_MATHJAX]

$ is small, use

Unable to parse markup [type=CF_MATHJAX]

$; if

Unable to parse markup [type=CF_MATHJAX]

$ is small, use

Unable to parse markup [type=CF_MATHJAX]

$. - This gives

Unable to parse markup [type=CF_MATHJAX]

$. Since

Unable to parse markup [type=CF_MATHJAX]

$, if

Unable to parse markup [type=CF_MATHJAX]

$, the work is roughly bounded by

Unable to parse markup [type=CF_MATHJAX]

$. - If

Unable to parse markup [type=CF_MATHJAX]

$ is small, brute force as described above. - If

Unable to parse markup [type=CF_MATHJAX]

$ is small, consider every pair of positions

Unable to parse markup [type=CF_MATHJAX]

$. Delete these two positions from each string by concatenating the hashes of

Unable to parse markup [type=CF_MATHJAX]

$,

Unable to parse markup [type=CF_MATHJAX]

$, and

Unable to parse markup [type=CF_MATHJAX]

$, treating empty ranges as empty strings. - If two resulting hashes match, the original strings differ only at the deleted positions, so their Hamming distance is at most

Unable to parse markup [type=CF_MATHJAX]

$.

Code

Problem 8

Problem

  • This problem is misleading at first. The operation shifts all rows by the same amount

Unable to parse markup [type=CF_MATHJAX]

$ and all columns by the same amount

Unable to parse markup [type=CF_MATHJAX]

$. We choose one pair

Unable to parse markup [type=CF_MATHJAX]

$. - If we copy the matrix to its right, every cyclic horizontal shift becomes a contiguous window. Copying it downward does the same for vertical shifts. - The problem becomes finding the lexicographically smallest

Unable to parse markup [type=CF_MATHJAX]

$ submatrix in the doubled matrix. - Use 2D hashing: hash each row, then combine the row hashes vertically to support submatrix comparisons. - Consider each top-left position

Unable to parse markup [type=CF_MATHJAX]

$. To compare two candidates, binary search for the first differing row, then the first differing character in that row, and keep the lexicographically smaller candidate.

Code

Problem 9

Problem

  • This problem seems hard at first, but the key is

Unable to parse markup [type=CF_MATHJAX]

$, which suggests a DP with about

Unable to parse markup [type=CF_MATHJAX]

$ states. - The pieces we select from the original string do not have to be adjacent. Once we have formed a prefix of the target string, we only need to know how far that prefix extends. - Let

Unable to parse markup [type=CF_MATHJAX]

$ be the maximum target-prefix length we can form using at most

Unable to parse markup [type=CF_MATHJAX]

$ pieces from the first

Unable to parse markup [type=CF_MATHJAX]

$ characters of the original string. - We can skip the current character. Alternatively, if

Unable to parse markup [type=CF_MATHJAX]

$, find the longest common prefix of the suffixes starting at

Unable to parse markup [type=CF_MATHJAX]

$ and

Unable to parse markup [type=CF_MATHJAX]

$ using hashing and binary search. - If that common-prefix length is

Unable to parse markup [type=CF_MATHJAX]

$, update

Unable to parse markup [type=CF_MATHJAX]

$ with

Unable to parse markup [type=CF_MATHJAX]

$.

Code

Problem 10

Problem

  • This problem seems impossible at first, but the trick is clever.
  • For each sequence

Unable to parse markup [type=CF_MATHJAX]

$, we can calculate its total Hamming distance to all other sequences. However, this does not prove that every distance equals

Unable to parse markup [type=CF_MATHJAX]

$: distances

Unable to parse markup [type=CF_MATHJAX]

$ and

Unable to parse markup [type=CF_MATHJAX]

$ still add up to

Unable to parse markup [type=CF_MATHJAX]

$. - The idea is to assign each sequence

Unable to parse markup [type=CF_MATHJAX]

$ a random weight

Unable to parse markup [type=CF_MATHJAX]

$. - Let

Unable to parse markup [type=CF_MATHJAX]

$ be the Hamming distance and

Unable to parse markup [type=CF_MATHJAX]

$. Check whether

Unable to parse markup [type=CF_MATHJAX]

$. - If every distance from

Unable to parse markup [type=CF_MATHJAX]

$ to another sequence is

Unable to parse markup [type=CF_MATHJAX]

$, the equality always holds. Otherwise, random weights make accidental cancellation unlikely. - We can compute the weighted distance sums efficiently by collecting the total weight for each character at each position.

Code

Extension: XOR Equality

Theory

  • Assign each distinct value

Unable to parse markup [type=CF_MATHJAX]

$ a random hash

Unable to parse markup [type=CF_MATHJAX]

$. - For a set

Unable to parse markup [type=CF_MATHJAX]

$, define

Unable to parse markup [type=CF_MATHJAX]

$. - Once these hashes are maintained, comparing

Unable to parse markup [type=CF_MATHJAX]

$ and

Unable to parse markup [type=CF_MATHJAX]

$ takes

Unable to parse markup [type=CF_MATHJAX]

$ time. This gives a randomized check for set equality. - Each distinct value must be included once. XORing every occurrence instead only tracks the parity of its frequency.

Problem 11

Problem

  • To count valid subarrays

Unable to parse markup [type=CF_MATHJAX]

$, use prefix states so that the state of a subarray is determined by the states at

Unable to parse markup [type=CF_MATHJAX]

$ and

Unable to parse markup [type=CF_MATHJAX]

$. - Here, we need frequencies modulo

Unable to parse markup [type=CF_MATHJAX]

$. Ordinary XOR tracks parity, so instead hash the vector of frequencies modulo

Unable to parse markup [type=CF_MATHJAX]

$. - A subarray passes this check when

Unable to parse markup [type=CF_MATHJAX]

$, meaning that every frequency is divisible by

Unable to parse markup [type=CF_MATHJAX]

$, subject to hash collisions. - But a frequency could be

Unable to parse markup [type=CF_MATHJAX]

$, while we need every appearing value to occur exactly

Unable to parse markup [type=CF_MATHJAX]

$ times. - Maintain the minimum allowed prefix index

Unable to parse markup [type=CF_MATHJAX]

$ so that no value appears more than

Unable to parse markup [type=CF_MATHJAX]

$ times in the subarray. Combined with the modulo-

Unable to parse markup [type=CF_MATHJAX]

$ condition, this leaves only frequencies

Unable to parse markup [type=CF_MATHJAX]

$ and

Unable to parse markup [type=CF_MATHJAX]

$.

Code Thank for reading.

История

 
 
 
 
Правки
 
 
  Rev. Язык Кто Когда Δ Комментарий
en6 Английский theoneandonlytron 2026-10-13 03:50:58 1756
en5 Английский theoneandonlytron 2026-10-13 03:37:44 17947
en4 Английский theoneandonlytron 2026-10-13 03:29:28 0 (published)
en3 Английский theoneandonlytron 2026-10-13 03:29:28 15978 (published)
en2 Английский theoneandonlytron 2026-10-13 03:22:09 6137
en1 Английский theoneandonlytron 2026-10-13 02:22:09 1723 Initial revision (saved to drafts)