Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Essential Number Theory and Linear Algebra Algorithms

Tech May 8 3
  1. Number Theory Fundamentals

Extended Euclidean Algorithm and Linear Diophantine Equations

For integers a and b, the equation ax + by = d has integer solutions if and only if the greatest common divisor gcd(a, b) divides d. This is known as Bézout's Identity. The extended Euclidean algorithm alows us to find x and y such that ax + by = gcd(a, b).

The logic relies on the observation that ax + by = bx' + (a mod b)y'. Substituting a mod b = a - floor(a/b) * b, we can derive the recursive relationship for the coefficients.

long long extended_gcd(long long a, long long b, long long& coeff_a, long long& coeff_b) {
    if (b == 0) {
        // Base case: a * 1 + 0 * 0 = a
        coeff_a = 1;
        coeff_b = 0;
        return a;
    }
    long long x1, y1;
    long long gcd_val = extended_gcd(b, a % b, x1, y1);
    
    // Update coefficients based on the recursive step
    coeff_a = y1;
    coeff_b = x1 - (a / b) * y1;
    
    return gcd_val;
}

Modular Arithmetic and Multiplicative Inverses

If m divides (a - b), we say a ≡ b (mod m). Modular arithmetic preserves properties under addition, subtraction, and multiplication. However, division is distinct; it requires the concept of a multiplicative inverse.

The multiplicative inverse of x modulo p, denoted x-1, satisfies x * x-1 ≡ 1 (mod p). To solve ax ≡ 1 (mod b), we use the extended Euclidean algorithm to find a solution to ax + by = 1.

#include <iostream>
using namespace std;

long long extended_gcd(long long a, long long b, long long& x, long long& y) {
    if (b == 0) { x = 1; y = 0; return a; }
    long long g = extended_gcd(b, a % b, x, y);
    long long t = x; x = y; y = t - a / b * y;
    return g;
}

int main() {
    long long a, mod;
    if (!(cin >> a >> mod)) return 0;
    
    long long x, y;
    extended_gcd(a, mod, x, y);
    
    // Normalize the result to the smallest positive integer
    long long inverse = (x % mod + mod) % mod;
    cout << inverse << endl;
    return 0;
}

Prime Number Sieves

The Sieve of Eratosthenes is a efficient method to find all primes up to n with O(n log log n) complexity. The Linear Sieve (or Euler's Sieve) improves upon this by ensuring each composite number is marked only once by its smallest prime factor, resulting in O(n) complexity.

#include <vector>
#include <iostream>
using namespace std;

const int MAX_N = 10005;
vector<int> primes;
bool is_composite[MAX_N];

void linear_sieve(int limit) {
    for (int i = 2; i <= limit; ++i) {
        if (!is_composite[i]) {
            primes.push_back(i);
        }
        for (int prime : primes) {
            long long multiple = (long long)i * prime;
            if (multiple > limit) break;
            is_composite[multiple] = true;
            if (i % prime == 0) break; // Key: ensures smallest prime factor marks the number
        }
    }
}

int main() {
    int n = 100;
    linear_sieve(n);
    for (int p : primes) cout << p << " ";
    return 0;
}

Factorial Prime Factorization

To decompose n! into prime factors, we calculate the exponent of each prime p. The exponent is given by the sum: floor(n/p) + floor(n/p2) + .... This represents the count of multiples of p, p2, etc., in 1 to n.

  1. Linear Algebra and Exponentiation

Fast Exponentiation (Binary Exponentiation)

This technique computes ab mod m in O(log b) time by decomposing the exponent b into its binary representation.

long long fast_pow(long long base, long long exp, long long mod) {
    long long result = 1;
    base %= mod;
    while (exp > 0) {
        if (exp & 1) result = (result * base) % mod;
        base = (base * base) % mod;
        exp >>= 1;
    }
    return result;
}

Gaussian Elimination

Gaussian elimination solves systems of linear equations Ax = B. It transforms the augmented matrix [A|B] into row-echelon form using elementary row operations (swapping rows, multiplying a row by a scalar, adding a multiple of one row to another).

Application: Finding a Sphere's Center
Given n+1 points on an n-dimensional sphere, the center can be found by solving linear equations derived from equating the squared distances from the center to any two points. This transforms quadratic equations into linear ones.

Application: Equipment Selection
Given equipment with attribute vectors and costs, we want to maximize the number of items purchased while minimizing cost, ensuring selected attribute vectors are linearly independent. We use Gaussian elimination with a greedy strategy: when selecting a pivot row, we choose the cheapest available option.

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

const double EPS = 1e-9;
const int MAX_DIM = 510;

int n, m;
double attributes[MAX_DIM][MAX_DIM]; // Matrix A
int costs[MAX_DIM]; // Cost vector

int solve_equipment() {
    int current_rank = 0;
    int total_cost = 0;

    for (int col = 0; col < m && current_rank < n; ++col) {
        int selected_row = -1;
        // Greedy: Find the row with non-zero coefficient in this column and minimal cost
        for (int row = current_rank; row < n; ++row) {
            if (fabs(attributes[row][col]) > EPS && 
                (selected_row == -1 || costs[row] < costs[selected_row])) {
                selected_row = row;
            }
        }

        if (selected_row == -1) continue; // No valid vector for this column

        // Swap current row with selected row
        swap(attributes[current_rank], attributes[selected_row]);
        swap(costs[current_rank], costs[selected_row]);

        total_cost += costs[current_rank];

        // Eliminate this variable from other rows
        for (int row = 0; row < n; ++row) {
            if (row != current_rank && fabs(attributes[row][col]) > EPS) {
                double factor = attributes[row][col] / attributes[current_rank][col];
                for (int k = col; k < m; ++k) {
                    attributes[row][k] -= attributes[current_rank][k] * factor;
                }
            }
        }
        current_rank++;
    }

    cout << current_rank << " " << total_cost << endl;
    return 0;
}

  1. Permutations, Combinations, and Dynamic Programming

Theoretical Foundations

Key formulas for permutations P(n, m) and combinations C(n, m) are standard. An important property for modular arithmetic is Lucas' Theorem, which allows computing C(n, m) modulo a prime p by decomposing n and m in to base p digits.

Combinatorial Grouping Problem

Problem: Group n distinguishable people. Person i must be in a group of size at most ai. Groups are indistinguishable, and order within a group does not matter. Find the number of valid grouping schemes modulo 998244353.

Analysis: We use Dynamic Programming. Let dp[i][j] be the number of ways to form groups of sizes strictly greater than i using j remaining people. We process group sizes from large to small.

#include <iostream>
#include <vector>
using namespace std;

const long long MOD = 998244353;
const int MAX_N = 2010;

int n;
int limits[MAX_N];      // a[i]
int freq[MAX_N];        // frequency of limit = i
long long dp[MAX_N][MAX_N];
long long comb[MAX_N][MAX_N];
long long fact[MAX_N], inv_fact[MAX_N];

long long fast_pow(long long base, long long exp) {
    long long res = 1;
    while (exp) {
        if (exp & 1) res = res * base % MOD;
        base = base * base % MOD;
        exp >>= 1;
    }
    return res;
}

void precompute() {
    // Combinations
    for (int i = 0; i <= n; ++i) {
        comb[i][0] = 1;
        for (int j = 1; j <= i; ++j) {
            comb[i][j] = (comb[i-1][j] + comb[i-1][j-1]) % MOD;
        }
    }
    // Factorials and Inverses
    fact[0] = 1;
    for (int i = 1; i <= n; ++i) fact[i] = fact[i-1] * i % MOD;
    inv_fact[n] = fast_pow(fact[n], MOD - 2);
    for (int i = n - 1; i >= 0; --i) inv_fact[i] = inv_fact[i + 1] * (i + 1) % MOD;
}

int main() {
    ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        int val; cin >> val;
        freq[val]++;
    }
    precompute();

    // Base case: group size > n means 0 people left
    dp[n + 1][0] = 1;
    
    for (int size = n; size >= 1; --size) {
        long long inv_pow_size = 1;
        // Iterate number of groups of current 'size'
        for (int k = 0; size * k <= n; ++k) {
            // Term: (size*k)! / ((size!)^k * k!)
            long long ways_to_partition = fact[size * k] * inv_pow_size % MOD * inv_fact[k] % MOD;
            
            for (int rem = 0; rem + size * k <= n; ++rem) {
                // Transition: choose size*k people from remaining, partition them, add to previous state
                long long ways_choose = comb[rem + size * k][size * k];
                dp[size][rem] = (dp[size][rem] + 
                                 dp[size + 1][rem + size * k - freq[size]] * ways_choose % MOD * 
                                 ways_to_partition % MOD) % MOD;
            }
            inv_pow_size = inv_pow_size * inv_fact[size] % MOD;
        }
    }
    
    cout << dp[1][0] << endl;
    return 0;
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.