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
Excellent solution OleGG, But your code is not optimized. I have made following changes to your code,
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.
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:
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.
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.
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.
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.
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.
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 ;) )
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]