All Euler problems
Project Euler

Prime Triplets

Build a triangle of positive integers: Row n contains n numbers. Two numbers are neighbours if they occupy adjacent positions in the triangle (up to 6 neighbours per position: left, right, and up t...

Source sync Apr 19, 2026
Problem #0196
Level Level 09
Solved By 3,054
Languages C++, Python
Answer 322303240771079935
Length 641 words
number_theorygeometrygraph

Problem Statement

This archive keeps the full statement, math, and original media on the page.

Build a triangle from all positive integers in the following way:

\[ \begin {array}{ccccccccccc} 1 & & & & & & & & & & \\ \textcolor {red}{2} & \textcolor {red}{3} & & & & & & & & & \\ 4 & \textcolor {red}{5} & 6 & & & & & & & & \\ \textcolor {red}{7} & 8 & 9 & 10 & & & & & & & \\ 11 & 12 & \textcolor {red}{13} & 14 & 15 & & & & & & \\ 16 & \textcolor {red}{17} & 18 & \textcolor {red}{19} & 20 & 21 & & & & & \\ 22 & \textcolor {red}{23} & 24 & 25 & 26 & 27 & 28 & & & & \\ \textcolor {red}{29} & 30 & \textcolor {red}{31} & 32 & 33 & 34 & 35 & 36 & & & \\ \textcolor {red}{37} & 38 & 39 & 40 & \textcolor {red}{41} & 42 & \textcolor {red}{43} & 44 & 45 & & \\ 46 & \textcolor {red}{47} & 48 & 49 & 50 & 51 & 52 & \textcolor {red}{53} & 54 & 55 & \\ 56 & 57 & 58 & \textcolor {red}{59} & 60 & \textcolor {red}{61} & 62 & 63 & 64 & 65 & 66 \\ & & & & & \ldots & & & & & \end {array} \]

Each positive integer has up to eight neighbours in the triangle.

A set of three primes is called a prime triplet if one of the three primes has the other two as neighbours in the triangle.

For example, in the second row, the prime numbers \(2\) and \(3\) are elements of some prime triplet.

If row \(8\) is considered, it contains two primes which are elements of some prime triplet, i.e. \(29\) and \(31\).

If row \(9\) is considered, it contains only one prime which is an element of some prime triplet: \(37\).

Define \(S(n)\) as the sum of the primes in row \(n\) which are elements of any prime triplet.

Then \(S(8)=60\) and \(S(9)=37\).

You are given that \(S(10000)=950007619\).

Find \(S(5678027) + S(7208785)\).

Problem 196: Prime Triplets

Mathematical Foundation

Theorem 1. (Triangle Indexing.) Row nn (1-indexed) contains the integers from T(n)=n(n1)2+1T(n) = \frac{n(n-1)}{2} + 1 to n(n+1)2\frac{n(n+1)}{2}. Position kk (0-indexed) in row nn has value T(n)+kT(n) + k.

Proof. Row 1 has 1 element, row 2 has 2, …, row n1n-1 has n1n-1. The total count before row nn is i=1n1i=n(n1)2\sum_{i=1}^{n-1} i = \frac{n(n-1)}{2}. Thus row nn starts at n(n1)2+1\frac{n(n-1)}{2} + 1. \square

Theorem 2. (Neighbour Structure.) Position (n,k)(n, k) has at most 6 neighbours:

  • Same row: (n,k1)(n, k-1) and (n,k+1)(n, k+1) (when they exist).
  • Row above (n1n-1): positions (n1,k1)(n-1, k-1) and (n1,k)(n-1, k) (when valid, i.e., 0k10 \leq k-1 and kn2k \leq n-2).
  • Row below (n+1n+1): positions (n+1,k)(n+1, k) and (n+1,k+1)(n+1, k+1).

Proof. In the triangular arrangement, each position touches at most 2 positions in its own row (left and right), 2 in the row above (up-left and up-right), and 2 in the row below (down-left and down-right). The index mapping follows from the triangular geometry. \square

Lemma 1. (Parity Constraint.) For n4n \geq 4, two positions in the same row that are adjacent hold consecutive integers, so at most one can be prime (since one is even). Therefore, prime triplets in large rows must have the “center” prime’s two prime neighbours in adjacent rows, not in the same row.

Proof. Adjacent positions in row nn hold values T(n)+kT(n) + k and T(n)+k+1T(n) + k + 1, which are consecutive integers. For values >2> 2, at most one of two consecutive integers is prime. \square

Theorem 3. (Membership Criterion.) A prime pp at position (n,k)(n, k) belongs to a prime triplet if and only if at least one of the following holds:

  1. (Center condition:) pp has at least 2 prime neighbours.
  2. (Spoke condition:) pp has a prime neighbour qq that itself has at least 2 prime neighbours (so qq is the center, and pp is one of the spokes).

Proof. A prime triplet is a set {p,q,r}\{p, q, r\} where one element (say qq) is a neighbour of both pp and rr. If pp is in such a triplet, either pp is the center (condition 1, with qq and rr as its prime neighbours) or pp is a spoke (condition 2, where qq is the center and has another prime neighbour rr). These exhaust all possibilities. \square

Lemma 2. (Search Radius.) To evaluate conditions 1 and 2 for primes in row nn, we need primality information for rows n2n-2 through n+2n+2. Condition 2 requires checking neighbours of neighbours, which can reach 2 rows away.

Proof. A neighbour of a position in row nn is in row n1n-1, nn, or n+1n+1. A neighbour of that neighbour can be in rows n2n-2 through n+2n+2. \square

Editorial

A prime triplet is three primes where ONE has the other TWO as neighbors. Neighbors of element (n, k) include up to 8 positions: same row: (n, k-1), (n, k+1) row above: (n-1, k-1), (n-1, k), (n-1, k+1) row below: (n+1, k-1), (n+1, k), (n+1, k+1) A prime p is in a triplet iff: (a) p has >= 2 prime neighbors, OR (b) p has a prime neighbor q that has >= 2 prime neighbors S(n) = sum of primes in row n that are in any prime triplet. Find S(5678027) + S(7208785). Note: This Python solution demonstrates the algorithm on small inputs. For the actual large inputs, use the C++ solution (takes ~5 minutes). We determine the range of values in rows n-2 to n+2. We then segmented sieve: find all primes in [lo, hi]. Finally, first sieve small primes up to sqrt(hi).

Pseudocode

Determine the range of values in rows n-2 to n+2
Segmented sieve: find all primes in [lo, hi]
First sieve small primes up to sqrt(hi)
Segmented sieve for [lo, hi]
For each prime p in row n:
for k from 0 to n-1
Count prime neighbours of v
Check spoke condition: does any prime neighbour have >= 2 prime neighbours?

Complexity Analysis

  • Time: The values in row nn are Θ(n2)\Theta(n^2). The segmented sieve covers O(n)O(n) values in 5 rows, requiring small primes up to O(n)O(n). Sieving small primes: O(nloglogn)O(n \log \log n). Segmented sieve: O(nloglogn)O(n \log \log n). Neighbour checks: O(n)O(n) with constant factor (at most 6 neighbours, each with at most 6 neighbours). Total: O(nloglogn)O(n \log \log n).
  • Space: O(n)O(n) for the sieve bitmap over 5 rows, plus O(n/lnn)O(n / \ln n) for small primes.

Answer

322303240771079935\boxed{322303240771079935}

Code

Each problem page includes the exact C++ and Python source files from the local archive.

C++ project_euler/problem_196/solution.cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

// Problem 196: Prime Triplets
//
// Triangle: row n has values T(n)+0, T(n)+1, ..., T(n)+n-1 where T(n) = n*(n-1)/2 + 1.
//
// Neighbors of (n, k) [0-indexed]:
//   Same row: (n, k-1), (n, k+1)
//   Row above (n-1): (n-1, k-1), (n-1, k), (n-1, k+1) [if valid]
//   Row below (n+1): (n+1, k-1), (n+1, k), (n+1, k+1) [if valid]
//   Up to 8 neighbors total.
//
// Prime triplet: 3 primes where ONE has the other TWO as neighbors.
// NOT a clique -- a star/path with the center having 2+ prime neighbors.
//
// A prime p in row n is in a triplet iff:
//   (a) p has >= 2 prime neighbors, OR
//   (b) p has a prime neighbor q that has >= 2 prime neighbors (q is center, p is spoke)
//
// Algorithm:
// 1. Sieve primes in rows n-3 to n+3 (need 3-row buffer to check neighbors of neighbors)
// 2. Count prime neighbors for each prime in rows n-2 to n+2
// 3. Mark primes in row n that satisfy (a) or (b)

ll row_start(ll n) {
    return n * (n - 1) / 2 + 1;
}

vector<char> segmented_sieve(ll lo, ll hi) {
    ll range = hi - lo + 1;
    vector<char> is_prime(range, 1);
    if (lo <= 0) {
        for (ll i = 0; i < min(range, 1 - lo + 1); i++)
            is_prime[i] = 0;
    }
    if (lo <= 1 && 1 <= hi) is_prime[1 - lo] = 0;

    ll sqrt_hi = (ll)sqrt((double)hi) + 2;
    vector<char> small(sqrt_hi + 1, 1);
    small[0] = small[1] = 0;
    for (ll i = 2; i <= sqrt_hi; i++) {
        if (small[i]) {
            for (ll j = i * i; j <= sqrt_hi; j += i)
                small[j] = 0;
            ll start = max(i * i, ((lo + i - 1) / i) * i);
            for (ll j = start; j <= hi; j += i)
                is_prime[j - lo] = 0;
        }
    }
    return is_prime;
}

ll solve(ll n) {
    // Need rows n-3 to n+3 for the full neighbor-of-neighbor check
    ll r_min = max(1LL, n - 3);
    ll r_max = n + 3;
    ll lo = row_start(r_min);
    ll hi = row_start(r_max) + r_max - 1;

    auto sieve = segmented_sieve(lo, hi);

    auto prime = [&](ll v) -> bool {
        if (v < lo || v > hi) return false;
        return sieve[v - lo];
    };

    // For each prime in rows n-2..n+2, count how many prime neighbors it has.
    // A prime with >= 2 prime neighbors is a "center" of at least one triplet.
    // Store: for each cell in rows n-2..n+2, the count of prime neighbors.

    // We'll work row by row. For efficiency, store prime neighbor counts.
    // But we only need to know: for each prime in rows n-1, n, n+1,
    // does it have >= 2 prime neighbors?
    // And for primes in row n: also check if any neighbor has >= 2 prime neighbors.

    // Step 1: For each prime in rows n-2..n+2, count prime neighbors
    // (neighbors span rows r-1..r+1, so we need rows n-3..n+3 in the sieve)

    // For memory efficiency: store "has_2_prime_nbs" flag for primes in rows n-2..n+2
    // Then for row n: check condition (a) or (b).

    // Actually let's just compute for rows n-1, n, n+1 whether each prime has >= 2 prime nbs.
    // For condition (b), a prime p in row n needs a neighbor q in rows n-1 or n+1 (or n)
    // that has >= 2 prime neighbors. q's neighbors span rows n-2..n+2.

    // So we need "has_2_prime_nbs" for primes in rows n-1, n, n+1.
    // To compute that, we need prime status in rows n-2..n+2.
    // The sieve covers rows n-3..n+3, which is sufficient.

    // Build arrays of "has >= 2 prime neighbors" for rows n-1, n, n+1
    auto count_prime_nbs = [&](ll row, ll pos) -> int {
        int cnt = 0;
        ll rs = row_start(row);
        ll v = rs + pos;
        // Same row
        if (pos > 0 && prime(v - 1)) cnt++;
        if (pos < row - 1 && prime(v + 1)) cnt++;
        // Row above
        if (row > 1) {
            ll above = row_start(row - 1);
            for (int dk = -1; dk <= 1; dk++) {
                ll p = pos + dk;
                if (p >= 0 && p < row - 1) {
                    if (prime(above + p)) cnt++;
                }
            }
        }
        // Row below
        {
            ll below = row_start(row + 1);
            for (int dk = -1; dk <= 1; dk++) {
                ll p = pos + dk;
                if (p >= 0 && p < row + 1) {
                    if (prime(below + p)) cnt++;
                }
            }
        }
        return cnt;
    };

    // For each prime in row n, check:
    // (a) it has >= 2 prime neighbors
    // (b) it has a prime neighbor (in rows n-1, n, n+1) that has >= 2 prime neighbors

    ll n_start = row_start(n);
    ll result = 0;

    for (ll k = 0; k < n; k++) {
        ll v = n_start + k;
        if (!prime(v)) continue;

        int my_cnt = count_prime_nbs(n, k);
        if (my_cnt >= 2) {
            result += v;
            continue;
        }

        // Check condition (b): does any prime neighbor have >= 2 prime neighbors?
        bool found = false;

        // Same row neighbors
        if (k > 0 && prime(v - 1)) {
            if (count_prime_nbs(n, k - 1) >= 2) { found = true; }
        }
        if (!found && k < n - 1 && prime(v + 1)) {
            if (count_prime_nbs(n, k + 1) >= 2) { found = true; }
        }

        // Row above neighbors
        if (!found && n > 1) {
            ll above = row_start(n - 1);
            for (int dk = -1; dk <= 1 && !found; dk++) {
                ll p = k + dk;
                if (p >= 0 && p < n - 1 && prime(above + p)) {
                    if (count_prime_nbs(n - 1, p) >= 2) { found = true; }
                }
            }
        }

        // Row below neighbors
        if (!found) {
            ll below = row_start(n + 1);
            for (int dk = -1; dk <= 1 && !found; dk++) {
                ll p = k + dk;
                if (p >= 0 && p < n + 1 && prime(below + p)) {
                    if (count_prime_nbs(n + 1, p) >= 2) { found = true; }
                }
            }
        }

        if (found) result += v;
    }

    return result;
}

int main() {
    ios_base::sync_with_stdio(false);
    ll ans = solve(5678027) + solve(7208785);
    cout << ans << endl;
    return 0;
}