28

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 and B are lucky? 1 <= A <= B <= 1018. I tried this.

  • First I generated all possible primes between 1 and the number that could be resulted by summing the squares (81 *18 = 1458).

  • I read in A and B find out maximum number that could be generated by summing the digits If B is a 2 digit number ( the max number is 18 generated by 99).

  • For each prime number between 1 an max number. I applied integer partition algorithm.

  • For each possible partition I checked whether their sum of squares of their digits form prime. If so the possible permutations of that partition are generated and if they lie with in range they are lucky numbers.

This is the implementation:

#include<stdio.h>
#include<malloc.h>
#include<math.h>
#include <stdlib.h>
#include<string.h>
long long luckynumbers;
int primelist[1500];

int checklucky(long long possible,long long a,long long b){
    int prime =0;
    while(possible>0){
            prime+=pow((possible%10),(float)2);
            possible/=10;
    }
        if(primelist[prime]) return 1;
        else return 0;
}
long long getmax(int numdigits){
        if(numdigits == 0) return 1; 
        long long maxnum =10;
             while(numdigits>1){
                        maxnum = maxnum *10;
                        numdigits-=1;
             }
         return maxnum; 

}
void permuteandcheck(char *topermute,int d,long long a,long long b,int digits){
    if(d == strlen(topermute)){
            long long possible=atoll(topermute);
            if(possible >= getmax(strlen(topermute)-1)){  // to skip the case of getting already read numbers like 21 and 021(permuted-210

                if(possible >= a && possible <= b){

                    luckynumbers++;
                }
            }
    }
    else{
        char lastswap ='\0';
        int i;
        char temp;
        for(i=d;i<strlen(topermute);i++){
            if(lastswap == topermute[i])
                continue;
            else
                lastswap = topermute[i];
            temp = topermute[d];
            topermute[d] = topermute[i];
            topermute[i] = temp;

            permuteandcheck(topermute,d+1,a,b,digits);

            temp = topermute[d];
            topermute[d] = topermute[i];
            topermute[i] = temp;
        }

    }

}


void findlucky(long long possible,long long a,long long b,int digits){
    int i =0;
    if(checklucky(possible,a,b)){
        char topermute[18];
        sprintf(topermute,"%lld",possible);
        permuteandcheck(topermute,0,a,b,digits);
    }
}


void  partitiongenerator(int k,int n,int numdigits,long long  possible,long long a,long long b,int digits){
    if(k > n || numdigits > digits-1 || k > 9) return;
    if(k == n){

        possible+=(k*getmax(numdigits));

        findlucky(possible,a,b,digits);
        return;
    }
    partitiongenerator(k,n-k,numdigits+1,(possible + k*getmax(numdigits)),a,b,digits);
    partitiongenerator(k+1,n,numdigits,possible,a,b,digits);

}


void calcluckynumbers(long long a,long long b){
    int i;
    int numdigits = 0;
    long long temp = b;
    while(temp > 0){
        numdigits++;
        temp/=10;
    }

    long long maxnum =getmax(numdigits)-1;
    int maxprime=0,minprime =0;
    temp = maxnum;
    while(temp>0){
        maxprime+=(temp%10);
        temp/=10;
    }
    int start = 2;
    for(;start <= maxprime ;start++){
            if(primelist[start]) {
                partitiongenerator(0,start,0,0,a,b,numdigits);
            }
    }   

}   
void generateprime(){
    int i = 0;
    for(i=0;i<1500;i++)
        primelist[i] = 1;
    primelist[0] = 0;
    primelist[1] = 0;
    int candidate = 2;
    int topCandidate = 1499;
    int thisFactor = 2;
    while(thisFactor * thisFactor <= topCandidate){
        int  mark = thisFactor + thisFactor;
        while(mark <= topCandidate){
            *(primelist + mark) = 0;
            mark += thisFactor;
        }
        thisFactor++;
        while(thisFactor <= topCandidate && *(primelist+thisFactor) == 0) thisFactor++;
    }

}
int main(){
        char input[100];
        int cases=0,casedone=0;
    long long a,b;
    generateprime();
        fscanf(stdin,"%d",&cases);
        while(casedone < cases){
        luckynumbers = 0;
                fscanf(stdin,"%lld %lld",&a,&b);
        int i =0;
               calcluckynumbers(a,b);
                casedone++;
        }

}

The algorithm is too slow. I think the answer can be found based on the property of numbers.Kindly share your thoughts. Thank you.

vgeta
  • 507
  • 1
  • 9
  • 15

10 Answers10

15

Excellent solution OleGG, But your code is not optimized. I have made following changes to your code,

  1. It does not require to go through 9*9*i for k in count_lucky function, because for 10000 cases it would run that many times, instead i have reduced this value through start and end.

  2. i have used ans array to store intermediate results. It might not look like much but over 10000 cases this is the major factor that reduces the time.

I have tested this code and it passed all the test cases. Here is the modified code:

    #include <stdio.h>

    const int MAX_LENGTH = 18;
    const int MAX_SUM = 162;
    const int MAX_SQUARE_SUM = 1458;
    int primes[1460];
    unsigned long long dyn_table[20][164][1460];
    //changed here.......1
    unsigned long long ans[19][10][164][1460];  //about 45 MB

    int start[19][163];
    int end[19][163];
    //upto here.........1
    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];
                    }
                }
            }
        }
    }

    unsigned long long count_lucky (unsigned long long maxp) {
        unsigned long long result = 0;
        int len = 0;
        int split_max[MAX_LENGTH];
        while (maxp) {
            split_max[len] = maxp % 10;
            maxp /= 10;
            ++len;
        }
        int sum = 0;
        int sq_sum = 0;
        unsigned long long step_result;
        unsigned long long step_;
        for (int i = len-1; i >= 0; --i) {
            step_result = 0;
            int x1 = 9*i;
            for (int l = 0; l < split_max[i]; ++l) {
    //changed here........2
                step_ = 0;
                if(ans[i][l][sum][sq_sum]!=0)
                    {
                        step_result +=ans[i][l][sum][sq_sum];
                        continue;
                    }
                int y = l + sum;
                int x = l*l + sq_sum;
                for (int j = 0; j <= x1; ++j) {
                    if(primes[j + y])
                        for (int k=start[i][j]; k<=end[i][j]; ++k) {
                            if (primes[k + x]) {
                                step_result += dyn_table[i][j][k];
                                step_+=dyn_table[i][j][k];
                            }
                    }

                }
                 ans[i][l][sum][sq_sum] = step_;
    //upto here...............2
            }
            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();

    //changed here..........3
        for(int i=0;i<=18;i++)
            for(int j=0;j<=163;j++)
                {
                    for(int k=0;k<=1458;k++)
                            if(dyn_table[i][j][k]!=0ll)
                                {
                                    start[i][j] = k;
                                    break;                               
                                }

                    for(int k=1460;k>=0;k--)
                            if(dyn_table[i][j][k]!=0ll)
                                {
                                    end[i][j]=k;
                                    break;                               
                                }
                }
    //upto here..........3
        int cases = 0;
        scanf("%d",&cases);
        for (int i = 0; i < cases; ++i) {
            unsigned long long a, b;

            scanf("%lld %lld", &a, &b);
    //changed here......4
            if(b == 1000000000000000000ll)
                b--;
    //upto here.........4
            printf("%lld\n", count_lucky(b) - count_lucky(a-1));
        }
        return 0;

}

Explanation:

gen_primes() and gen_table() are pretty much self explanatory.

count_lucky() works as follows:

split the number in split_max[], just storing single digit number for ones, tens, hundreds etc. positions. The idea is: suppose split_map[2] = 7, so we need to calculate result for

1 in hundreds position and all 00 to 99.

2 in hundreds position and all 00 to 99.

. .

7 in hundreds position and all 00 to 99.

this is actually done(in l loop) in terms of sum of digits and sum of square of digits which has been precalcutaled. for this example: sum will vary from 0 to 9*i & sum of square will vary from 0 to 9*9*i...this is done in j and k loops. This is repeated for all lengths in i loop

This was the idea of OleGG.

For optimization following is considered:

  1. its useless to run sum of squares from 0 to 9*9*i as for particular sums of digits it would not go upto the full range. Like if i = 3 and sum equals 5 then sum of square would not vary from 0 to 9*9*3.This part is stored in start[] and end[] arrays using precomputed values.

  2. value for particular number of digits and particular digit at most significant position of number and upto particular sum and upto particular sum of square isstored for memorization. Its too long but still its about 45 MB. I believe this could be further optimized.

pirate
  • 496
  • 6
  • 16
  • 1
    Congrats, you've done it. I knew the trick had to be storing intermediate results, and I even had the right idea about how those results would represent a breakdown of the original number (see my answer above with the "00000-54321" example). However, I failed to implement it properly with a [19][10][164][1460] array as you did. Bravo... this answer deserves a checkmark and many more upvotes. Sidenote: the trickery you do with k matters very little... the biggest gains come from `ans[]` (20x gain) and from moving `if(primes[j + y])` out of the loop (2x gain). For a total of ~40x gain. – The111 Jun 02 '12 at 08:00
  • Disregard my "20x" and "2x" comments above. Those only make sense for a certain test case I was running. Obviously it depends on the total number of test cases. Regardless, those are still the two biggest improvements. – The111 Jun 02 '12 at 08:06
  • 1
    InterviewStreet is supposed to be a personnal challenge! Giving advice and helping to get on the track is one thing, posting plain code solution is another... – Benny Nov 16 '12 at 15:26
  • 1
    i think giving full solution makes your idea more concrete. Anyways contest is long over so there is no harm done. It might not be appropriate but it sure is helpful. – pirate Nov 19 '12 at 05:06
14

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

OleGG
  • 8,589
  • 1
  • 28
  • 34
  • 1
    This is a very smart solution. Unfortunately, it doesn't work 100% since you can't split every number up into powers of 10. I had a different solution which also required a similar splitting, and could find no way to split a number such as 1234567, although 1100111 will split fine into powers of 10. Most numbers split into *multiples* of powers of 10 which doesn't quite work with your table I don't think. – The111 Feb 20 '12 at 02:32
  • 1
    Thank you for your comment, I've really found one bug in my algo, so I've updated it. Now I've tested it on all numbers from 1 to million and results was the same with straightforward solution. Cycle `for (int l = 0; l < split_max[i]; ++l)` allows us to deal not only with numbers like 1100111, but with all - I took into account that first digit in splitted part can be no only 0 or 1. – OleGG Feb 20 '12 at 10:21
  • Thanks for the update. I was trying to come up with a similar mod to your algorithm myself, but yours is better. :-) – The111 Feb 20 '12 at 17:05
  • As I mentioned in another comment on the OP (and in my own answer which I just posted), this is a puzzle from InterviewStreet.com. Your solution, while brilliant, is still not fast enough to solve their test set in the allotted time (5 seconds for an unknown number of test cases). I spent a significant amount of time extending your code with the result that the new version performs around 100x than your version (see my answer posted). However, even this is still not fast enough for the time constraints given. I can't seem to find any more optimizations... – The111 Feb 23 '12 at 23:54
  • 1
    Yep, I've done my solution for situation with single query, multiple queries in your solution are handled better, so +1 from me =) – OleGG Feb 24 '12 at 06:30
  • 1
    Still not fast enough though. Wracking my brain for days trying to find a better optimization. A very frustratingly addictive puzzle! – The111 Feb 25 '12 at 05:05
  • Can you collaborate more on count_lucky()? I can't get the logic behind it. Thanks :) – Islam Hassan Aug 30 '12 at 05:40
4

Instead of enumerating the space of numbers, enumerate the different "signatures" of numbers that are lucky. and then print all the differnet combination of those.

This can be done with trivial backtracking:

#define _GNU_SOURCE
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

#define bitsizeof(e)   (CHAR_BIT * sizeof(e))
#define countof(e)     (sizeof(e) / sizeof((e)[0]))
#define BITMASK_NTH(type_t, n) ( ((type_t)1) << ((n) & (bitsizeof(type_t) - 1)))
#define OP_BIT(bits, n, shift, op) \
    ((bits)[(unsigned)(n) / (shift)] op BITMASK_NTH(typeof(*(bits)), n))
#define TST_BIT(bits, n)    OP_BIT(bits, n, bitsizeof(*(bits)), &  )
#define SET_BIT(bits, n)    (void)OP_BIT(bits, n, bitsizeof(*(bits)), |= )

/* fast is_prime {{{ */

static uint32_t primes_below_1M[(1U << 20) / bitsizeof(uint32_t)];

static void compute_primes_below_1M(void)
{
    SET_BIT(primes_below_1M, 0);
    SET_BIT(primes_below_1M, 1);
    for (uint32_t i = 2; i < bitsizeof(primes_below_1M); i++) {
        if (TST_BIT(primes_below_1M, i))
            continue;
        for (uint32_t j = i * 2; j < bitsizeof(primes_below_1M); j += i) {
            SET_BIT(primes_below_1M, j);
        }
    }
}

static bool is_prime(uint64_t n)
{
    assert (n < bitsizeof(primes_below_1M));
    return !TST_BIT(primes_below_1M, n);
}

/* }}} */

static uint32_t prime_checks, found;

static char     sig[10];
static uint32_t sum, square_sum;

static void backtrack(int startdigit, int ndigits, int maxdigit)
{
    ndigits++;

    for (int i = startdigit; i <= maxdigit; i++) {
        sig[i]++;
        sum        += i;
        square_sum += i * i;
        prime_checks++;
        if (is_prime(sum) && is_prime(square_sum)) {
                found++;
        }
        if (ndigits < 18)
            backtrack(0, ndigits, i);
        sig[i]--;
        sum        -= i;
        square_sum -= i * i;
    }
}

int main(void)
{
    compute_primes_below_1M();
    backtrack(1, 0, 9);

    printf("did %d signature checks, found %d lucky signatures\n",
           prime_checks, found);
    return 0;
}

When I run it it does:


$ time ./lucky
did 13123091 signature checks, found 933553 lucky signatures
./lucky  0.20s user 0.00s system 99% cpu 0.201 total

Instead of found++ you want to generate all the distinct permutations of digits that you can build with that number. I also precompute the first 1M of primes ever.

I've not checked if the code is 100% correct, you may have to debug it a bit. But the rought idea is here, and I'm able to generate all the lucky permutation below 0.2s (even without bugs it should not be more than twice as slow).

And of course you want to generate the permutations that verify A <= B. You may want to ignore generating partitions that have more digits than B or less than A too. Anyway you can improve on my general idea from here.

(Note: The blurb at the start is because I cut and paste code I wrote for project euler, hence the very fast is_prime that works for N <= 1M ;) )

Pierre Habouzit
  • 690
  • 4
  • 6
3

For those who weren't aware already, this is a problem on the website InterviewStreet.com (and in my opinion, the most difficult one there). My approach started off similar to (and was inspired by) OleGG's below. However, after creating the first [19][163][1459] table that he did (which I'll call table1), I went in a slightly different direction. I created a second table of ragged length [19][x][3] (table2), where x is the number of unique sum pairs for the corresponding number of digits. And for the third dimension of the table, with length 3, the 1st element is the quantity of unique "sum pairs" with the sum and squareSum values held by the 2nd and 3rd elements.

For example:

//pseudocode

table2[1] = new long[10][3]
table2[1] = {{1, 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}}

table2[2] = new long[55][3]
table2[3] = new long[204][3]
table2[4] = new long[518][3]
    .
    .
    .
    .
table2[17] = new long[15552][3]
table2[18] = new long[17547][3]

The numbers I have for the second dimension length of the array (10, 55, 204, 518, ..., 15552, 17547) can be verified by querying table1, and in a similar fashion table2 can be populated. Now using table2 we can solve large "lucky" queries much faster than OleGG's posted method, although still employing a similar "split" process as he did. For example, if you need to find lucky(00000-54321) (i.e. the lucky numbers between 0 and 54321), it breaks down to the sum of the following 5 lines:

lucky(00000-54321) = {
    lucky(00000-49999) +
    lucky(50000-53999) +
    lucky(54000-54299) +
    lucky(54300-53319) +
    lucky(54320-54321)
}

Which breaks down further:

lucky(00000-49999) = {
    lucky(00000-09999) +
    lucky(10000-19999) +
    lucky(20000-29999) +
    lucky(30000-39999) +
    lucky(40000-49999)
}
    .
    .
lucky(54000-54299) = {
    lucky(54000-54099) +
    lucky(54100-54199) +
    lucky(54200-54299)
}
    .
    .
    .
    etc

Each of these values can be obtained easily by querying table2. For example, lucky(40000-49999) is found by adding 4 and 16 to the 2nd and 3rd elements of the third dimension table2:

sum = 0
for (i = 0; i < 518; i++)
    if (isPrime[table2[4][i][1] + 4] && isPrime[table2[4][i][2] + 4*4])
        sum += table2[4][i][0]
return sum

Or for lucky(54200-54299):

sum = 0
for (i = 0; i < 55; i++)
    if (isPrime[table2[2][i][1] + (5+4+2)]
    && isPrime[table2[2][i][2] + (5*5+4*4+2*2)])
        sum += table2[2][i][0]
return sum

Now, OleGG's solution performed significantly faster than anything else I'd tried up until then, but with my modifications described above, it performs even better than before (by a factor of roughly 100x for a large test set). However, it is still not nearly fast enough for the blind test cases given on InterviewStreet. Through some clever hack I was able to determine I am currently running about 20x too slow to complete their test set in the allotted time. However, I can find no further optimizations. The biggest time sink here is obviously iterating through the second dimension of table2, and the only way to avoid that would be to tabulate the results of those sums. However, there are too many possibilities to compute them all in the time given (5 seconds) or to store them all in the space given (256MB). For example, the lucky(54200-54299) loop above could be pre-computed and stored as a single value, but if it was, we'd also need to pre-compute lucky(123000200-123000299) and lucky(99999200-99999299), etc etc. I've done the math and it is way too many calculations to pre-compute.

The111
  • 5,757
  • 4
  • 39
  • 55
1

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 .

zuozuo
  • 376
  • 2
  • 7
0

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.

jsc
  • 156
  • 1
  • 3
0

Based on the requirements, you can do it in different ways. If I was doing it, I would calculate the prime numbers using 'Sieve of Eratosthenes' in the required range (A to (9*2)*B.length), cache them (again, depending on your setup, you can use in-memory or disk cache) and use it for the next run.

I just coded a fast solution (Java), as below (NOTE: Integer overflow is not checked. Just a fast example. Also, my code is not optimized.):

import java.util.ArrayList;
import java.util.Arrays;

public class LuckyNumbers {
    public static void main(String[] args) {
        int a = 0, b = 1000;
        LuckyNumbers luckyNums = new LuckyNumbers();
        ArrayList<Integer> luckyList = luckyNums.findLuckyNums(a, b);
        System.out.println(luckyList);
    }

    private ArrayList<Integer> findLuckyNums(int a, int b) {
        ArrayList<Integer> luckyList = new ArrayList<Integer>();
        int size = ("" + b).length();        
        int maxNum = 81 * 4; //9*2*b.length() - 9 is used, coz it's the max digit
        System.out.println("Size : " + size + " MaxNum : " + maxNum);
        boolean[] primeArray = sieve(maxNum);

        for(int i=a;i<=b;i++) {
            String num = "" + i;
            int sumDigits = 0;
            int sumSquareDigits = 0;

            for(int j=0;j<num.length();j++) {
                int digit = Integer.valueOf("" + num.charAt(j));
                sumDigits += digit;
                sumSquareDigits += Math.pow(digit, 2);
            }

            if(primeArray[sumDigits] && primeArray[sumSquareDigits]) {
                luckyList.add(i);
            }
        }

        return luckyList;
    }

    private boolean[] sieve(int n) {
        boolean[] prime = new boolean[n + 1];
        Arrays.fill(prime, true);
        prime[0] = false;
        prime[1] = false;
        int m = (int) Math.sqrt(n);

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

        return prime;
    }
}

And the output was:

[11, 12, 14, 16, 21, 23, 25, 32, 38, 41, 49, 52, 56, 58, 61, 65, 83, 85, 94, 101, 102, 104, 106, 110, 111, 113, 119, 120, 131, 133, 137, 140, 146, 160, 164, 166, 173, 179, 191, 197, 199, 201, 203, 205, 210, 223, 229, 230, 232, 250, 289, 292, 298, 302, 308, 311, 313, 317, 320, 322, 331, 335, 337, 344, 346, 353, 355, 364, 368, 371, 373, 377, 379, 380, 386, 388, 397, 401, 409, 410, 416, 434, 436, 443, 449, 461, 463, 467, 476, 490, 494, 502, 506, 508, 520, 533, 535, 553, 559, 560, 566, 580, 595, 601, 605, 610, 614, 616, 634, 638, 641, 643, 647, 650, 656, 661, 665, 674, 683, 689, 698, 713, 719, 731, 733, 737, 739, 746, 764, 773, 779, 791, 793, 797, 803, 805, 829, 830, 836, 838, 850, 863, 869, 883, 892, 896, 904, 911, 917, 919, 922, 928, 937, 940, 944, 955, 968, 971, 973, 977, 982, 986, 991]

bchetty
  • 2,231
  • 1
  • 19
  • 26
  • 3
    Unfortunately, your algorithm will be still slow where (B - A) -> 10^18, cause it is still linear – OleGG Jan 26 '12 at 14:20
  • 1
    In my implementation I have used Eratosthenes method. That's trivial. The main thing to be optimized is traversing all numbers and calculating sum of their digits because the numbers range from 1 to 10^18. – vgeta Jan 26 '12 at 14:22
  • the range of primes is actually much smaller – MK. Jan 26 '12 at 14:24
  • I agree that this is slower too. Maybe this can be sped up by using multiple machines and MapReduce frameworks? or is there any other faster solution, for a program running on a single machine? – bchetty Jan 26 '12 at 14:30
  • Nope. no parallelism, just one machine. I believe that the secret lies in property of numbers. – vgeta Jan 26 '12 at 14:41
  • Secret is in using DP. I'll show my solution in minutes, and I think it'll be much faster than O(N) – OleGG Jan 26 '12 at 14:47
0

I haven't carefully analyzed your current solution but this might improve on it:

Since the order of digits doesn't matter, you should go through all possible combinations of digits 0-9 of length 1 to 18, keeping track of the sum of digits and their squares and adding one digit at a time, using result of previous calculation.

So if you know that for 12 sum of digits is 3 and of squares is 5, look at numbers 120, 121, 122... etc and calculate sums for them trivially from the 3 and 5 for 12.

MK.
  • 33,605
  • 18
  • 74
  • 111
  • The integer partition algorithm does that. And also each partition is permuted. – vgeta Jan 26 '12 at 14:34
  • 1
    wait, why do you need to permute? You should be able to calculate the number of permutations using the formula? – MK. Jan 26 '12 at 15:03
  • take 11 the sum is 2.. 110 and 101 also produce the same sum. – vgeta Jan 26 '12 at 15:06
  • 3
    you just need to look at 11, then 110 because you can always keep your numbers when enumerating in order. There is no need to look at both 110 and 101, if you generate the combinations of numbers ordered. Then once you know that 110 is good, you will increment your count by the number of possible permutations of these 3 digits (Being careful to exclude the ones with leading 0s). – MK. Jan 26 '12 at 15:09
0

Sometimes the fastest solution is incredibly simple:

uint8_t precomputedBitField[] = {
    ...
};

bool is_lucky(int number) {
    return precomputedBitField[number >> 8] & (1 << (number & 7));
}

Just modify your existing code to generate "precomputedBitField".

If you're worried about size, to cover all numbers from 0 to 999 it will only cost you 125 bytes, so this method will probably be smaller (and a lot faster) than any other alternative.

Brendan
  • 35,656
  • 2
  • 39
  • 66
  • Bitwise operations costs nothing compared to all algorithm complexity. Also, array of prime numbers can easily lie into processor's cache and getting number from it will be even faster than performing operations on array – OleGG Jan 26 '12 at 16:32
  • 1
    I am sorry. I am not able to understand this solution. Can you please throw some more light on it. Thanks – vgeta Jan 26 '12 at 20:42
  • It is not a new solution, but change to existing to make array of prime numbers fit better in memory. For our case it wouldn't be significant – OleGG Jan 27 '12 at 14:36
-2

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,

Pedro Teran
  • 1,200
  • 3
  • 17
  • 43
  • I dont think the "Lucky numbers" generated by sieve are the "lucky numbers" I am looking for. "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" – vgeta Feb 19 '12 at 21:07