Algorithm to find Lucky Numbers

前端 未结 10 974
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-22 21:02

I came across this question.A number is called lucky if the sum of its digits, as well as the sum of the squares of its digits is a prime number. How many numbers between A

相关标签:
10条回答
  • 2020-12-22 21:58

    I was trying to come up with a solution using Pierre's enumeration method, but never came up with a sufficiently fast way to count the permutations. OleGG's counting method is very clever, and pirate's optimizations are necessary to make it fast enough. I came up with one minor improvement, and one workaround to a serious problem.

    First, the improvement: you don't have to step through all the sums and squaresums one by one checking for primes in pirate's j and k loops. You have (or can easily generate) a list of primes. If you use the other variables to figure out which primes are in range, you can just step through the list of suitable primes for sum and squaresum. An array of primes and a lookup table to quickly determine at which index the prime >= a number is at is helpful. However, this is probably only a fairly minor improvement.

    The big issue is with pirate's ans cache array. It is not 45MB as claimed; with 64 bit entries, it is something like 364MB. This is outside the (current) allowed memory limits for C and Java. It can be reduced to 37MB by getting rid of the "l" dimension, which is unnecessary and hurts cache performance anyway. You're really interested in caching counts for l + sum and l*l + squaresum, not l, sum, and squaresum individually.

    0 讨论(0)
  • 2020-12-22 22:00

    You should use DP for this task. Here is my solution:

    #include <stdio.h>
    
    const int MAX_LENGTH = 18;
    const int MAX_SUM = 162;
    const int MAX_SQUARE_SUM = 1458;
    int primes[1459];
    long long dyn_table[19][163][1459];
    
    void gen_primes() {
        for (int i = 0; i <= MAX_SQUARE_SUM; ++i) {
            primes[i] = 1;
        }
        primes[0] = primes[1] = 0;
    
        for (int i = 2; i * i <= MAX_SQUARE_SUM; ++i) {
            if (!primes[i]) {
                continue;
            }
            for (int j = 2; i * j <= MAX_SQUARE_SUM; ++j) {
                primes[i*j] = 0;
            }
        }
    }
    
    void gen_table() {
        for (int i = 0; i <= MAX_LENGTH; ++i) {
            for (int j = 0; j <= MAX_SUM; ++j) {
                for (int k = 0; k <= MAX_SQUARE_SUM; ++k) {
                    dyn_table[i][j][k] = 0;
                }
            }
        }
        dyn_table[0][0][0] = 1;
    
        for (int i = 0; i < MAX_LENGTH; ++i) {
            for (int j = 0; j <= 9 * i; ++j) {
                for (int k = 0; k <= 9 * 9 * i; ++k) {
                    for (int l = 0; l < 10; ++l) {
                        dyn_table[i + 1][j + l][k + l*l] += dyn_table[i][j][k];
                    }
                }
            }
        }
    }
    
    long long count_lucky (long long max) {
                long long result = 0;
        int len = 0;
        int split_max[MAX_LENGTH];
        while (max) {
            split_max[len] = max % 10;
            max /= 10;
            ++len;
        }
        int sum = 0;
        int sq_sum = 0;
        for (int i = len-1; i >= 0; --i) {
            long long step_result = 0;
            for (int l = 0; l < split_max[i]; ++l) {
                for (int j = 0; j <= 9 * i; ++j) {
                    for (int k = 0; k <= 9 * 9 * i; ++k) {
                        if (primes[j + l + sum] && primes[k + l*l + sq_sum]) {
                            step_result += dyn_table[i][j][k];
                        }
                    }
                }
            }
            result += step_result;
    
            sum += split_max[i];
            sq_sum += split_max[i] * split_max[i];
        }
    
        if (primes[sum] && primes[sq_sum]) {
            ++result;
        }
    
        return result;
    }
    
    int main(int argc, char** argv) {
        gen_primes();
        gen_table();
    
        int cases = 0;
        scanf("%d", &cases);
        for (int i = 0; i < cases; ++i) {
            long long a, b;
            scanf("%lld %lld", &a, &b);
            printf("%lld\n", count_lucky(b) - count_lucky(a-1));
        }
        return 0;
    }
    

    Brief explanation:

    • I'm calculating all primes up to 9 * 9 * MAX_LENGTH using Eratosthenes method;
    • Later, using DP, I'm building table dyn_table where value X in dyn_table[i][j][k] means that we have exactly X numbers of length i with sum of digits equal to j and sum of its squares equal to k
    • Then we can easily count amount of lucky numbers from 1 to 999..999(len times of 9). For this we just sum up all dyn_table[len][j][k] where both j and k are primes.
    • To calculate amount of lucky number from 1 to random X we split interval from 1 to X into intervals with length equal to 10^K (see *count_lucky* function).
    • And our last step is subtract count_lucky(a-1) (cause we are including a in our interval) from count_lucky(b).

    That's all. Precalculation work for O(log(MAX_NUMBER)^3), each step have also this complexity.

    I've tested my solution against linear straightforward one and results were equal

    0 讨论(0)
  • 2020-12-22 22:00

    I have just solved this problem.

    It's just a Dynamic Programming problem. Take DP[n](sum-square_sum) as the DP function, and DP[n](sum-square_sum) is the count of all of the numbers whose digits is less than or equal to n, with the sum and square_sum of digits of the number is respectively represented by sum and square_sum. For example:

    DP[1](1-1) = 1      # only 1 satisfies the condition                        
    DP[2](1-1) = 2      # both 1 and 10 satisfies the condition                        
    DP[3](1-1) = 3      # 1 10 100
    DP[3](2-4) = 3      # 11 110 101
    

    Since we can easily figure out the first DP state DP[1][..][..], it is:

    (0-0) => 1     (1-1) => 1    (2-4) => 1     (3-9) => 1     (4-16) => 1    
    (5-25) => 1    (6-36) => 1   (7-49) => 1    (8-64) => 1    (9-81) => 1
    

    then we can deduce DP[1] from DP[1], and then DP[3] ... DP[18] the deduce above is made by the fact that every time when n increase by 1, for example from DP[1] to DP[2], we got a new digit (0..9), and the set of (sum, square_sum) pair (i.e. DP[n]) must be updated.

    Finally, we can traverse the DP[18] set and count of the numbers that are lucky.

    Well, how about the time and space complexity of the algorithm above? As we know sum <= 18*9=162, square_sum <= 18*9*9 = 1458, so the set of (sum, square_sum) pair (i.e. DP[n]) is very small, less than 162*1458=236196, in fact it's much smaller than 236196; The fact is: my ruby program counting all the lucky numbers between 0 and 10^18 finishes in less than 1s.

    ruby lucky_numbers.rb  0.55s user 0.00s system 99% cpu 0.556 total
    

    and I test my program by writing a test function using brute force algorithm, and it's right for numbers less than 10^7 .

    0 讨论(0)
  • 2020-12-22 22:09

    First I would like to add that a lucky number can be calculated by a sieve, the explanation of the sieve can be found here: http://en.wikipedia.org/wiki/Lucky_number

    so you can improve your solution speed using a sieve to determine the numbers,

    0 讨论(0)
提交回复
热议问题