Breder.org

IBM Ponder This, March 2024

Solution to the IBM Ponder This problem for March 2024. Here is the problem statement, and here is the official solution.

X_1000	115192665
X_2024	117778830159

Approach

A prime sieve is initially populated by following Sieve of Eratosthenes. As an optimization, a bitset is used (instead of individual bytes) for 8-fold memory savings.

The whole sieve still took around 30 GB of RAM, which just about my system memory. An additional optimization could be populating the sieve contiguous segments at a time (instead of walking the whole memory space for each prime) to leverage RAM NUMA caching. (This is called the “Segmented Sieve” approach).

After that, a brute force approach is used, testing every integer starting from 1. The numbers seem to be all safely within the 64-bit range, so they can be efficiently computed by a modern 64-bit processor (without slow big integer libraries).

The code below uses a single core and takes around one hour of wall clock time on my machine to both compute the sieve and find the solutions for X_i, with i from 1 to 2024. The solution from the previous i-1 is the starting point for the brute force search for X_i to save on some recomputation.

Source code

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

typedef int64_t i64;
typedef uint64_t u64;


// #define END 1000
// #define MAX_N 120000000LL
// #define SQRT_MAX_N 11000LL

#define END 2024
#define MAX_N 250000000000LL
#define SQRT_MAX_N 500000LL

#define BASE 64
u64 bitset[(MAX_N/BASE)+1];
#define bitset_test(X) (bitset[((u64)(X))/BASE] & ((u64)1 << (((u64)(X)) % BASE)))
#define bitset_set(X) do{ bitset[((u64)(X))/BASE] |= ((u64)1 << (((u64)(X)) % BASE)); }while(0)

#undef assert
#define assert(X) if(!(X)) { \
	fprintf(stderr, "%s:%d: assert failed '%s'\n", __FILE__, __LINE__, #X); exit(1); }

static int is_prime(i64 x) {
	assert(x < MAX_N);
	return !bitset_test(x);
}

static int is_valid_fast(i64 n, i64 i) {
	if (is_prime(i)) { return 0; }
	i64 x = i;
	for (i64 k = 1; k < n; k++) {
		x = x + k;
		if (is_prime(x)) { return 0; }
	}
	return 1;
}

static i64 solve_fast(i64 n, i64 hint) {
	for (i64 i = hint; i < MAX_N; i++) {
		if (is_valid_fast(n, i)) {
			return i;
		}
	}
	return -1;
}

static void populate_sieve() {
	memset(bitset, 0, sizeof(bitset));
	bitset_set(1);
	for (i64 i = 2; i <= SQRT_MAX_N; i++) {
		if (bitset_test(i)) { continue; }
		for (i64 k = i*i; k <= MAX_N; k += i) {
			bitset_set(k);
		}
	}
}

int main() {
	fprintf(stderr, "Computing sieve up to %ld\n", MAX_N);
	populate_sieve();
	fprintf(stderr, "done\n");

	i64 hint = 1;
	for (int i = 1; i <= END; i++) {
		i64 sol = solve_fast(i, hint);
		if (hint != sol || i == END) {
			printf("%d\t%ld\n", i, sol);
			fflush(stdout);
		}
		hint = sol;
	}
}