RustP: Competitive Programming in Rust

website·Indexed 31 days ago

https://rustp.org/

A comprehensive resource for competitive programming using the Rust language. It provides implementations and tutorials on data structures (vectors, segment trees), number theory (GCD, LCM, Sieve of Eratosthenes, modular arithmetic), combinatorics, dynamic programming (0/1 Knapsack, Coin Change), and array algorithms like Kadane’s Algorithm and Prefix Sum Arrays, along with Codeforces problem solutions and basic I/O templates.

Tokens
26.8K
Snippets
97
Records
122
Agent score
56%

What's inside RustP

  1. Overview of Number Theory in Competitive Programming

    Number Theory in the context of competitive programming involves the study and application of integers and integral value functions. Key areas of focus include:

    • Arithmetic operations: Multiplication, exponents, and remainders.
    • Number factors: Greatest Common Divisor (GCD), Least Common Multiple (LCM), and prime numbers.
    • Factorials, Permutations, and Combinations.
    • Integer sequences: Fibonacci series, prime numbers, and square numbers.
  2. Overview of Segment Trees for Range Queries

    A Segment Tree is a binary tree-like data structure used to perform range queries (e.g., sum, product, min/max, GCD, or bitwise operations) on a subarray. It reduces the time complexity of range queries from O(n) to O(log n), while requiring O(n) auxiliary space.
  3. Understand Linear vs Non-Linear Data Structures in Rust

    Data structures are categorized by how they organize data:

    • Linear Data Structures: Data is stored sequentially. Each element has at most one preceding and one succeeding element. Examples include Arrays, Linked Lists, Stacks, Queues, and Strings.
    • Non-Linear Data Structures: Data is stored without a definite sequential order, allowing for multiple child elements. Examples include Trees and Graphs.
  4. Perform low-level programming and hardware control in Rust

    Rust provides low-level programming capabilities similar to C and C++, including direct control over memory, the use of pointers and references, the ability to execute assembly language instructions, and support for embedded devices.
  5. Understand Rust Vector capacity and size

    Rust vectors are contiguous growable arrays. They often allocate more memory than currently needed to avoid frequent reallocations.

    • Size (Length): The number of elements currently stored in the vector.
    • Capacity: The total amount of space allocated in memory.

    When a vector's size exceeds its capacity, Rust reallocates a new chunk of memory (typically doubling the previous capacity) and copies the existing elements to the new location.

    fn main() {
        // Create a vector of length 5, with all elements as 0
        let mut vecky = vec![0; 5];
        println!("Size: {}, Capacity: {}", vecky.len(), vecky.capacity()); // Size: 5, Capacity: 5
    
        vecky.push(0);
        println!("Size: {}, Capacity: {}", vecky.len(), vecky.capacity()); // Size: 6, Capacity: 10
    }
  6. Handle large integer overflows using Modular Arithmetic in Rust

    In competitive programming, results for factorials, exponents, permutations, or combinations often exceed the capacity of Rust's largest integer types (i128 and u128, which store up to approximately 10^38). To prevent overflow and maintain efficiency, results are typically computed modulo a large prime number, commonly 10^9 + 7 (1,000,000,007).
  7. Prevent data races and leaks with the Borrow Checker

    The Borrow Checker is a core Rust feature that prevents data races and memory leaks. It functions similarly to reference counting and is implemented by default for standard Abstract Data Types (ADTs) such as Vectors and Dequeues.
  8. Ensure memory safety in Rust

    Rust prevents common memory errors found in C/C++, such as null pointers, dangling pointers, and data races. It also prevents out-of-bounds array access. For scenarios requiring more flexibility or direct hardware access, Rust provides an unsafe option to bypass some of these safety checks.
  9. Understand Rust's performance and memory efficiency

    Rust achieves high performance and memory efficiency by eliminating the need for a runtime Garbage Collector (GC) and using a compiler instead of an interpreter. This allows programs to run faster and use memory more predictably compared to languages with managed runtimes.
  10. Understand Prefix Sum Array concepts and properties

    A Prefix Sum Array p is derived from an array a such that each index i in p contains the sum of all elements in a from index 0 up to index i (p[i] = a[0] + ... + a[i]).

    Key properties include:

    • Recursive Relation: The element at index i is calculated as p[i] = a[i] + p[i-1] for i > 0.
    • Sorting: If the original array contains only positive elements, the prefix sum array is always sorted in ascending order.
    • Zero-Sum Subarrays: If two indices in the prefix sum array have the same value, the sum of the subarray between those two indices is 0.
    • Complexity: Generating the array takes O(n) time and O(n) space.
  11. Understand Modular Multiplicative Inverse

    The Modular Multiplicative Inverse of a number n with respect to p is a natural number x (where 1 ≤ x < p) such that (n * x) mod p = 1. This is used in modular arithmetic to replace the division operation. The inverse exists if and only if n and p are coprime (gcd(n, p) = 1). Common use cases include competitive programming for fractional results, calculating large permutations/combinations, and RSA cryptography.
  12. Solve Codeforces 1870C (Colorful Table) using a BTreeSet in Rust

    To solve the 'Colorful Table' problem, the goal is to find the smallest square that contains all occurrences of a specific color, given that all colors smaller than the current one have already been removed from the set of available indices.

    Algorithm:

    1. Store the indices of each color (from 1 to $k$) in a vector of vectors for quick lookup.
    2. Maintain a BTreeSet containing all currently available indices (initially all indices from $0$ to $n-1$).
    3. Iterate through colors from 1 to $k$:
      • If a color is not present in the array, the answer for that color is 0.
      • Otherwise, the edge length of the required square is determined by the difference between the current maximum index and minimum index present in the BTreeSet plus one: 1 + last_index - first_index.
      • The result for the color is $2 \times (1 + \text{last} - \text{first})$.
      • Remove all indices where the current color appears from the BTreeSet before moving to the next color.

    Complexity:

    • Time Complexity: $O(k + N \log N)$ due to BTreeSet operations.
    • Space Complexity: $O(k + N)$ to store indices.
    use std::collections::BTreeSet;
    use std::io::stdin;
    
    fn solve() {
        // Read n (length) and k (number of colors)
        let n_k = take_vector();
        let n = n_k[0];
        let k = n_k[1];
        let arr = take_vector();
    
        // Store indices of each color
        let mut indices = vec![Vec::new(); k + 1];
        for i in 0..n {
            indices[arr[i]].push(i);
        }
    
        // BTreeSet to track available indices
        let mut minimum = BTreeSet::new();
        for i in 0..n {
            minimum.insert(i);
        }
    
        for i in 1..=k {
            if indices[i].is_empty() {
                print!("0 ");
                continue;
            }
    
            // The square edge is defined by the current min and max indices in the set
            let first = *minimum.first().unwrap();
            let last = *minimum.last().unwrap();
            print!("{} ", 2 * (1 + last - first));
    
            // Remove indices of the current color for the next iteration
            for j in &indices[i] {
                minimum.remove(j);
            }
        }
        println!();
    }
    
    // Helper to read space-separated integers
    fn take_vector() -> Vec<usize> {
        let mut input = String::new();
        stdin().read_line(&mut input).unwrap();
        input.trim().split_whitespace().map(|x| x.parse().unwrap()).collect()
    }
    
    pub fn main() {
        let t = take_int();
        for _ in 0..t { solve(); }
    }
    
    fn take_int() -> usize {
        let mut input = String::new();
        stdin().read_line(&mut input).unwrap();
        input.trim().parse().unwrap()
    }