All Euler problems
Project Euler

Odd Triplets

Define f(N) as the number of triplets (a, b, c) with 0 < a <= b <= c, a + b + c <= N, and a + b + c = 0 (where + denotes bitwise XOR). Find f(10^12) mod 10^9.

Source sync Apr 19, 2026
Problem #0242
Level Level 13
Solved By 1,231
Languages C++, Python
Answer 997104142249036713
Length 489 words
modular_arithmeticdynamic_programmingdigit_dp

Problem Statement

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

Given the set \(\{1,2,\dots ,n\}\), we define \(f(n, k)\) as the number of its \(k\)-element subsets with an odd sum of elements. For example, \(f(5,3) = 4\), since the set \(\{1,2,3,4,5\}\) has four \(3\)-element subsets having an odd sum of elements, i.e.: \(\{1,2,4\}\), \(\{1,3,5\}\), \(\{2,3,4\}\) and \(\{2,4,5\}\).

When all three values \(n\), \(k\) and \(f(n, k)\) are odd, we say that they make an odd-triplet \([n,k,f(n, k)]\).

There are exactly five odd-triplets with \(n \le 10\), namely:

\([1,1,f(1,1) = 1]\), \([5,1,f(5,1) = 3]\), \([5,5,f(5,5) = 1]\), \([9,1,f(9,1) = 5]\) and \([9,9,f(9,9) = 1]\).

How many odd-triplets are there with \(n \le 10^{12}\)?

Problem 242: Odd Triplets

Mathematical Development

Definition 1. For non-negative integers a,b,ca, b, c, write a=iai2ia = \sum_{i} a_i 2^i, b=ibi2ib = \sum_i b_i 2^i, c=ici2ic = \sum_i c_i 2^i in binary. The XOR abca \oplus b \oplus c computes the bitwise sum modulo 2.

Theorem 1 (XOR-zero bit classification). Three non-negative integers satisfy abc=0a \oplus b \oplus c = 0 if and only if at every bit position ii the triple (ai,bi,ci)(a_i, b_i, c_i) belongs to the set

V={(0,0,0),  (0,1,1),  (1,0,1),  (1,1,0)}.\mathcal{V} = \{(0,0,0),\; (0,1,1),\; (1,0,1),\; (1,1,0)\}.

Proof. The XOR of three bits equals zero if and only if an even number (0 or 2) of them are 1. These are precisely the four patterns listed. Since XOR operates independently at each bit position, the global condition abc=0a \oplus b \oplus c = 0 is equivalent to the local condition at every bit. \blacksquare

Lemma 1 (Bit-sum structure). If abc=0a \oplus b \oplus c = 0, then at each bit position ii the arithmetic sum si=ai+bi+ci{0,2}s_i = a_i + b_i + c_i \in \{0, 2\}. Consequently:

a+b+c=i=0Lsi2iwith each si{0,2}.a + b + c = \sum_{i=0}^{L} s_i \cdot 2^i \quad\text{with each } s_i \in \{0, 2\}.

In particular, a+b+ca + b + c is always even.

Proof. From the four valid patterns: (0,0,0)(0,0,0) sums to 0, and each of (0,1,1)(0,1,1), (1,0,1)(1,0,1), (1,1,0)(1,1,0) sums to 2. Writing S=a+b+c=isi2iS = a + b + c = \sum_i s_i \cdot 2^i with si{0,2}s_i \in \{0,2\}, every term is divisible by 2, so SS is even. \blacksquare

Theorem 2 (Digit DP formulation). Write NN in binary as N=i=0Lni2iN = \sum_{i=0}^{L} n_i \cdot 2^i with ni{0,1}n_i \in \{0,1\}. We process bits from position LL (MSB) down to 00 (LSB), maintaining the state (carry,tight,ordab,ordbc)(\text{carry}, \text{tight}, \text{ord}_{ab}, \text{ord}_{bc}) where:

ComponentDomainMeaning
carry{0,1}\{0, 1\}Carry from position i1i-1 in the addition a+b+ca + b + c
tight{0,1}\{0, 1\}Whether a+b+ca + b + c remains bounded by NN at bits processed so far
ordab\text{ord}_{ab}{=,<}\{=, <\}Whether a=ba = b or a<ba < b at bits processed so far
ordbc\text{ord}_{bc}{=,<}\{=, <\}Whether b=cb = c or b<cb < c at bits processed so far

At each bit position ii, we enumerate the four patterns in V\mathcal{V}. For pattern with bit-sum si{0,2}s_i \in \{0, 2\}, the digit of a+b+ca + b + c at position ii is (si+carry)mod2(s_i + \text{carry}) \bmod 2, with new carry (si+carry)/2\lfloor(s_i + \text{carry})/2\rfloor.

Proof of correctness. We establish a bijection between valid triplets (a,b,c)(a, b, c) and accepting paths through the DP automaton.

(i) State transitions are exhaustive. At each bit, we try all four valid XOR-zero patterns, which is the complete set by Theorem 1.

(ii) Ordering is maintained exactly. If ordab==\text{ord}_{ab} = {=} (meaning a=ba = b on all higher bits), the pattern must satisfy aibia_i \le b_i to preserve aba \le b. If ai<bia_i < b_i, the ordering transitions to ordab=<\text{ord}_{ab} = {<}, after which any (ai,bi)(a_i, b_i) is permitted (since a<ba < b is already established). Analogously for ordbc\text{ord}_{bc}.

(iii) Sum constraint is correct. The carry-based representation computes a+b+ca + b + c digit by digit. Tightness tracks whether the partial sum can still equal NN at higher bits; once a digit of a+b+ca + b + c falls strictly below the corresponding digit of NN, all subsequent bits are unconstrained.

(iv) Termination. At i=1i = -1, accept if and only if carry=0\text{carry} = 0 (no overflow beyond the MSB). \blacksquare

Remark. The total state space has {0,1}×{0,1}×{=,<}×{=,<}=16|\{0,1\}| \times |\{0,1\}| \times |\{=,<\}| \times |\{=,<\}| = 16 states per bit position. With L+140L + 1 \approx 40 bit positions and 4 patterns per state, the DP evaluates at most 16×4×40=256016 \times 4 \times 40 = 2560 transitions.

Editorial

Count triplets (a,b,c) with 0 < a <= b <= c, a+b+c <= N, a XOR b XOR c = 0. N = 10^12, answer mod 10^9. Digit DP on the binary representation of N. XOR-zero constraint: at each bit, (a_i, b_i, c_i) in {(0,0,0),(0,1,1),(1,0,1),(1,1,0)}. State: (bit position, carry, tight, ordering of a<=b and b<=c). We enforce a <= b. We then enforce b <= c. Finally, update ordering.

Pseudocode

Enforce a <= b
Enforce b <= c
Update ordering
Compute sum digit and carry
Update tightness
if tight
else
Accept states with carry = 0

Complexity Analysis

  • Time. O(LSV)O(L \cdot |S| \cdot |\mathcal{V}|) where L=log2N40L = \lceil \log_2 N \rceil \approx 40, S=16|S| = 16 states, V=4|\mathcal{V}| = 4 patterns. Total: O(2560)O(2560) transitions — effectively constant.
  • Space. O(S)=O(16)=O(1)O(|S|) = O(16) = O(1).

Answer

997104142249036713\boxed{997104142249036713}

Code

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

C++ project_euler/problem_242/solution.cpp
/*
 * Project Euler Problem 242: Odd Triplets
 *
 * Count triplets (a,b,c) with 0 < a <= b <= c, a+b+c <= N, a^b^c = 0.
 * N = 10^12, answer mod 10^9.
 *
 * Digit DP on the binary representation of N.
 * Valid bit patterns: (0,0,0), (0,1,1), (1,0,1), (1,1,0).
 * State: (bit position, carry, tight, ordering).
 */

#include <bits/stdc++.h>
using namespace std;

static const long long MOD = 1000000000LL;

int bits[50];
int nbits;
map<tuple<int,int,int,int>, long long> memo;

int patterns[4][3] = {{0,0,0},{0,1,1},{1,0,1},{1,1,0}};

long long solve(int pos, int carry, int tight, int order) {
    if (pos < 0)
        return carry == 0 ? 1 : 0;

    auto key = make_tuple(pos, carry, tight, order);
    auto it = memo.find(key);
    if (it != memo.end()) return it->second;

    long long result = 0;
    int n_bit = bits[pos];

    for (int p = 0; p < 4; p++) {
        int ai = patterns[p][0], bi = patterns[p][1], ci = patterns[p][2];

        bool a_eq_b = (order == 0 || order == 1);
        bool b_eq_c = (order == 0 || order == 2);

        if (a_eq_b && ai > bi) continue;
        if (b_eq_c && bi > ci) continue;

        bool new_a_lt_b = (order == 2 || order == 3) || (ai < bi);
        bool new_b_lt_c = (order == 1 || order == 3) || (bi < ci);

        int new_order;
        if (!new_a_lt_b && !new_b_lt_c) new_order = 0;
        else if (!new_a_lt_b && new_b_lt_c) new_order = 1;
        else if (new_a_lt_b && !new_b_lt_c) new_order = 2;
        else new_order = 3;

        int s = ai + bi + ci + carry;
        int sum_bit = s % 2;
        int new_carry = s / 2;

        int new_tight;
        if (tight) {
            if (sum_bit > n_bit) continue;
            new_tight = (sum_bit == n_bit) ? 1 : 0;
        } else {
            new_tight = 0;
        }

        result = (result + solve(pos - 1, new_carry, new_tight, new_order)) % MOD;
    }

    memo[key] = result;
    return result;
}

int main() {
    ios_base::sync_with_stdio(false);

    long long N_val = 1000000000000LL;
    nbits = 0;
    long long tmp = N_val;
    while (tmp > 0) {
        bits[nbits++] = tmp & 1;
        tmp >>= 1;
    }

    long long ans = solve(nbits - 1, 0, 1, 0);
    ans = (ans - 1 + MOD) % MOD;
    cout << ans << endl;
    return 0;
}