srsonia98's blog

By srsonia98, 5 weeks ago, In English

Least common multiple (LCM), typically denoted by lcm(a, b), is the smallest positive integer that is divisible by both a and b. Number theory dictates that the product of gcd(a, b) and lcm(a, b) is ab for positive integers, a and b ie lcm is simply absolute value of ab divided by gcd(a, b).

When dealing with the greatest common divisor (GCD), the typical approach involves utilizing Bézout's identity, which builds upon the Euclidean theorem. Bézout's identity states that for integers a and b with a greatest common divisor d, there exist integers x and y such that ax + by = d. The extended Euclidean algorithm can be employed to compute a pair of Bézout coefficients efficiently.

static int gcd(int a, int b) {
    if (b == 0)return a;
    else return gcd(b, a % b); 
} 

static int lcm(int a, int b){ 
    return Math.abs(a * b) / gcd(a, b); 
} 

However, it's worth noting that this method may lead to integer overflow issues, particularly when dealing with large numbers. To mitigate this concern, it's advisable to use data types like BigInteger in Java or long long in C++ depending on the range of the numbers involved. In the realm of cybersecurity and data encryption, the GCD is crucial for public-key encryption systems like RSA, which rely on the challenge of factoring large prime numbers. However, real-world applications often involve exceptionally large numbers, necessitating the use of optimized algorithms such as the Stein's algorithm for efficient GCD computation.

public static int SteinGCD(int a, int b) {
    if (a == 0)return b;
    if (b == 0)return a;

    int k = 0;
    while ((a | b) & 1) == 0) {
        a >>= 1;
        b >>= 1;
        k++;
    }

    while ((a & 1) == 0)a >>= 1;

    do {
        while ((b & 1) == 0)
            b >>= 1;
        if (a > b) {
            int temp = a;
            a = b;
            b = temp;
        }
        b -= a;
    } while (b != 0);

    return a << k;
}

The Stein's algorithm for computing the greatest common divisor (GCD) offers several benefits, including efficiency and effectiveness in handling large integers. Its optimized approach reduces the number of arithmetic operations required compared to traditional methods like Euclid's algorithm, making it particularly advantageous for performance-critical applications. However, when calculating the GCD for purposes such as finding the least common multiple (LCM), where prime factorization is involved, employing techniques like the sieve of Eratosthenes may be more appropriate. This is because prime factorization provides a direct method for determining the LCM, leveraging the precomputed list of primes to efficiently factorize the numbers involved.

public static ArrayList<Integer> sieveOfEratosthenes(int n) {
    boolean[] prime = new boolean[n + 1];
    Arrays.fill(prime, true);
    prime[0] = prime[1] = false;

    for (int p = 2; p * p <= n; p++) {
        if (prime[p]) {
            for (int i = p * p; i <= n; i += p)
                prime[i] = false;
        }
    }

    ArrayList<Integer> primes = new ArrayList<>();
    for (int i = 2; i <= n; i++) {
        if (prime[i])
            primes.add(i);
    }
    return primes;
}

public static long lcm(int a, int b) {
    int max = Math.max(a, b);
    ArrayList<Integer> primes = sieveOfEratosthenes(max);
    long lcm = 1;
    
    for (int prime : primes) {
        int power = 1;
        while (a % prime == 0 || b % prime == 0) {
            power *= prime;
            if (a % prime == 0) a /= prime;
            if (b % prime == 0) b /= prime;
        }
        lcm *= power;
    }
    lcm *= a * b;
    return lcm;
}

The Simple Sieve of Eratosthenes is effective for finding primes up to a certain limit, but it encounters challenges when dealing with large values of n. Firstly, it requires an array of size O(n) to store all numbers up to n, which may exceed memory constraints. Additionally, the Simple Sieve algorithm lacks cache efficiency, making it inefficient for larger values of n due to poor locality of reference when traversing the array. To address these issues, the Segmented Sieve algorithm is employed.

public static ArrayList<Integer> segmentedSieve(int n) {
    int segmentSize = (int)Math.sqrt(n) + 1;
    ArrayList<Integer> primes = new ArrayList<>();

    for (int low = 0; low <= n; low += segmentSize) {
        int high = Math.min(low + segmentSize - 1, n);
        boolean[] prime = new boolean[segmentSize];
        Arrays.fill(prime, true);

        for (int p : primes) {
            int start = (low / p) * p;
            if (start < low)
                start += p;
            for (int j = start; j <= high; j += p)
                prime[j - low] = false;
        }

        for (int i = Math.max(2, low); i <= high; i++) {
            if (prime[i - low])
                primes.add(i);
        }
    }

    return primes;
}

While the segmented Sieve of Eratosthenes offers advantages such as reduced memory usage and improved cache efficiency by processing smaller ranges at a time, it still has limitations. One drawback is that it requires additional computation to sieve each segment, which can introduce overhead and impact performance, especially for very large ranges. In contrast, the Sieve of Atkin is known for its superior efficiency in generating prime numbers, particularly for sparse ranges, due to its optimized algorithms based on modulo arithmetic operations. By focusing on identifying prime numbers using more intricate mathematical principles, the Sieve of Atkin can often outperform the segmented Sieve of Eratosthenes in terms of speed and scalability, making it a preferred choice for certain prime number generation tasks, particularly when dealing with large or sparse ranges.

static void sieveOfAtkin(int limit) {
    boolean[] sieve = new boolean[limit + 1];
    for (int i = 0; i <= limit; i++)sieve[i] = false;

    for (int x = 1; x * x <= limit; x++) {
        for (int y = 1; y * y <= limit; y++) {
            int n = (4 * x * x) + (y * y);
            if (n <= limit && (n % 12 == 1 || n % 12 == 5))
                sieve[n] ^= true;

            n = (3 * x * x) + (y * y);
            if (n <= limit && n % 12 == 7)
                sieve[n] ^= true;

            n = (3 * x * x) - (y * y);
            if (x > y && n <= limit && n % 12 == 11)
                sieve[n] ^= true;
        }
    }

    for (int r = 5; r * r <= limit; r++) {
        if (sieve[r]) {
            for (int i = r * r; i <= limit; i += r * r)
                sieve[i] = false;
        }
    }
}

Apart from the Sieve of Eratosthenes and the Sieve of Atkin, there are several other sieve algorithms and similar methods for generating prime numbers, each with its own strengths and applications:

  • Sieve of Sundaram: Efficient for smaller ranges and when memory usage is a concern due to its more compact storage requirements.
  • Wheel Factorization: Efficient in certain ranges where the numbers have a specific pattern that allows for skipping multiples of certain small primes by exploiting number theory properties to skip multiples of certain small primes, reducing the number of candidates for primality testing
  • Sieve of Segmented Euler: cater to specific mathematical contexts and combines the efficiency of Euler's sieve with the memory advantages of segmentation.
  • Linear Sieve: Optimal for large ranges with limited memory (eg: embedded systems), where its linear memory usage and cache-friendly operations provide efficiency. Also excels in performance-critical applications and situations requiring dynamic prime number generation without precomputed storage.

These methods offer alternatives, each with its own trade-offs in terms of efficiency, memory usage, and implementation complexity. Depending on the specific requirements of the prime number generation task, one may choose to use these alternative sieves for their particular advantages. However, due to time constraints, I won't delve into detailed explanations or provide code examples for these methods.

Full text and comments »

  • Vote: I like it
  • +3
  • Vote: I do not like it

By srsonia98, history, 6 weeks ago, In English

Returning to competitive programming on Codeforces after a hiatus was an eye-opening experience. The impact of the pandemic on my coding skills was evident, reflected in a disappointing dip in my rating. However, this setback has only fueled my determination to reclaim lost ground and ascend to the ranks of Expert or even Candidate Master within a year. While the realism of this goal remains uncertain, I've embraced a steadfast commitment to accountability and progress tracking. To achieve this, I've pledged to solve a problem daily and share weekly reflections on my experiences. These reflections will serve as a window into my journey, accessible both on Codeforces and my Substack platform. By sharing insights and lessons learned, I aim to spark meaningful discussions and provide valuable content for fellow enthusiasts.

Embarking on this journey, I recognize the importance of maximizing the resources and opportunities offered by Codeforces. To this end, I eagerly seek recommendations, feedback, and suggestions from the vibrant community on how best to leverage the platform. Whether it's strategies for tackling competitive programming questions more efficiently or actively engaging with fellow enthusiasts, I'm open to exploring every avenue for growth.

Moreover, I'm keenly aware that Codeforces is just one facet of a broader ecosystem catering to aspiring and seasoned programmers alike. Therefore, I welcome insights into additional resources that can complement and enhance my journey towards mastery in competitive programming. Whether it's online courses, specialized forums, or coding communities beyond Codeforces, I'm eager to expand my toolkit and sharpen my skills.

As a working professional navigating the demands of both coding challenges and career advancement, I understand the importance of balance and efficiency. For fellow professionals juggling similar aspirations and responsibilities, I invite you to connect on my Instagram, where I share insights and strategies for navigating the software job market.

Feel free to comment/message if you're interested in forming study groups, ideally within time zones that align with PST (USA West Coast).

Full text and comments »

  • Vote: I like it
  • +10
  • Vote: I do not like it