Problem 322: Binomial Coefficients Divisible by 10

View on Project Euler

Project Euler Problem 322 Solution

EulerSolve provides an optimized solution for Project Euler Problem 322, Binomial Coefficients Divisible by 10, with C++, Python, Java, and a step-by-step mathematical explanation.

Problem Summary Let \(T(m,n)\) be the number of binomial coefficients $$\binom{i}{n}$$ that are divisible by \(10\), for $$n\le i\lt m.$$ We are given $$T(10^9,10^7-10)=989697000,$$ and we must compute $$T(10^{18},10^{12}-10).$$ Mathematical Approach 1) Shift from \(i\) to \(x=i-n\). Write $$i=n+x,$$ so \(x\) runs through $$0\le x\lt L,\qquad L=m-n.$$ Then the problem is to count how many values of $$\binom{n+x}{n}$$ are divisible by \(10\). 2) Divisible by \(10\) means divisible by both \(2\) and \(5\). So we study the prime valuations $$v_2\!\left(\binom{n+x}{n}\right),\qquad v_5\!\left(\binom{n+x}{n}\right).$$ The coefficient is divisible by \(10\) exactly when both are positive. 3) Kummer's theorem turns valuations into carry counts. For a prime \(p\), Kummer says $$v_p\!\left(\binom{n+x}{n}\right)=\text{number of carries when adding }n\text{ and }x\text{ in base }p.$$ Therefore $$\binom{n+x}{n}\not\equiv0\pmod p$$ if and only if the base-\(p\) addition \(n+x\) has no carry at all . 4) No-carry condition as a digit inequality. Let the base-\(p\) digits be $$n=\sum n_j p^j,\qquad x=\sum x_j p^j.$$ No carry is equivalent to $$n_j+x_j\le p-1\qquad\text{for every }j,$$ or, equivalently, $$x_j\le p-1-n_j\qquad\text{for every digit }j.$$ This is the Lucas-style digit condition used by the code. 5) Inclusion-exclusion for divisibility by \(10\)....

Detailed mathematical approach

Problem Summary

For positive integers \(i\), \(m\) and \(n\), let \(T(m,n)\) be the number of binomial coefficients \(\binom{i}{n}\) that are divisible by \(10\), the upper index running over \(n\le i\lt m\). The statement supplies the checkpoint \(T(10^9,10^7-10)=989697000\) and asks for \(T(10^{18},10^{12}-10)\). [1] Visiting them one at a time is hopeless: the range holds about \(10^{18}\) coefficients, the largest with some \(6.4\cdot10^{12}\) decimal digits.

The whole solution in one sentence: \(\binom{i}{n}\) is divisible by \(10\) exactly when adding \(n\) and \(i-n\) carries in base \(2\) and also in base \(5\), so we count the carry-free cases in each base by scanning digits, count those carry-free in both by listing the base-\(5\) ones and testing each with one bitwise operation, and combine the three counts by inclusion–exclusion.

Mathematical Approach

1. Shifting the upper index

Write \(i=n+x\). As \(i\) runs over \(n\le i\lt m\), the shift \(x\) runs over \(0\le x\lt L\), where

$$L=m-n.$$

We must count the shifts \(x\) in that range for which \(\binom{n+x}{n}\) is divisible by \(10\). This form is the useful one: the arithmetic of \(\binom{n+x}{n}\) is governed by the addition \(n+x\). For the checkpoint \(L=990000010\); for the target \(L=999999000000000010\).

2. Divisibility by 10 splits into two primes

Since \(10=2\cdot5\) with the primes distinct, an integer is divisible by \(10\) exactly when it is divisible by \(2\) and by \(5\). Writing \(v_p(k)\) for the exponent of \(p\) in \(k\), a coefficient \(C\) is divisible by \(10\) precisely when both of

$$v_2(C)\ge1,\qquad v_5(C)\ge1$$

hold. So all we need of \(\binom{n+x}{n}\) is whether each exponent is zero or positive.

3. Legendre's formula and a carry identity

Legendre's formula counts one factor of \(p\) for each multiple of \(p\) up to \(k\), one more for each multiple of \(p^2\), and so on; there are \(\lfloor k/p^j\rfloor\) multiples of \(p^j\), and for instance \(6!=720=2^4\cdot45\) with \(3+1\) factors of \(2\). Hence [3]

$$v_p(k!)=\sum_{j\ge1}\lfloor k/p^j\rfloor.$$

Applying this to \(\binom{n+x}{n}=(n+x)!/(n!\,x!)\) and abbreviating

$$S_j=\lfloor (n+x)/p^j\rfloor,\quad N_j=\lfloor n/p^j\rfloor$$

and \(X_j=\lfloor x/p^j\rfloor\) gives

$$v_p\!\left(\binom{n+x}{n}\right)=\sum_{j\ge1}(S_j-N_j-X_j).$$

Now add \(n\) and \(x\) in base \(p\) by the school method. Let \(n_j\), \(x_j\) be their digits and \(c_j\) the carry leaving position \(j-1\), so \(c_0=0\) and \(c_{j+1}=1\) exactly when \(n_j+x_j+c_j\ge p\). Adding the bottom \(j\) digits of \(n\) and of \(x\) gives the bottom \(j\) digits of \(n+x\) plus that carry:

$$(n\bmod p^j)+(x\bmod p^j)=((n+x)\bmod p^j)+c_jp^j.$$

Splitting \(n+x\) at \(p^j\) in the two available ways also gives

$$\begin{aligned}S_jp^j+((n+x)\bmod p^j)&=N_jp^j+X_jp^j\\&\quad+(n\bmod p^j)+(x\bmod p^j).\end{aligned}$$

Substituting the first identity into the second, cancelling the common remainder and dividing by \(p^j\), leaves

$$S_j=N_j+X_j+c_j.$$

4. Kummer's theorem and the carry-free condition

Two small additions first: \(11_2+1_2=100_2\) carries twice and \(\binom43=4\) has \(v_2=2\), while \(11_2+10_2=101_2\) and \(3_5+2_5=10_5\) carry once each and \(\binom53=10\) is divisible by \(2\) and by \(5\). Substituting \(S_j-N_j-X_j=c_j\) into Section 3 gives Kummer's theorem of 1852: [2]

$$v_p\!\left(\binom{n+x}{n}\right)=\sum_{j\ge1}c_j,$$

the total number of carries in the base-\(p\) addition \(n+x\). So \(\binom{n+x}{n}\) escapes \(p\) exactly when that addition is carry-free, and carry-freeness is a plain digit condition:

$$n_j+x_j\le p-1$$

at every position \(j\). Both directions follow by induction on \(j\): if every digit pair obeys the inequality, then \(c_0=0\) gives \(n_j+x_j+c_j=n_j+x_j\le p-1\) and hence \(c_{j+1}=0\), so no carry appears; conversely, if all carries vanish then \(n_j+x_j\le p-1\) throughout. It can also be read off the coefficient: with no carries the digits of \(n+x\) are \(n_j+x_j\), so \(n_j\le n_j+x_j\) everywhere, exactly the case in which the digit product of Lucas's theorem is nonzero modulo \(p\). [4]

5. The two carry-free sets

Fix \(n\) and the range \(0\le x\lt L\), and define

  • \(A_2\), the number of shifts whose base-\(2\) addition is carry-free;
  • \(A_5\), the number whose base-\(5\) addition is carry-free;
  • \(A_{2,5}\), the number that are carry-free in both bases.

In base \(2\) the digits are \(0\) and \(1\), so \(n_j+x_j\le1\) says that \(n\) and \(x\) never have a \(1\) in the same position — a single machine instruction,

$$x\mathbin{\&}n=0,$$

where \(\mathbin{\&}\) is the bitwise and. In base \(5\) the condition is the digit cap

$$x_j\le 4-n_j,$$

which leaves \(5-n_j\) choices for digit \(j\) and forces \(x_j=0\) wherever \(n_j=4\).

6. Inclusion–exclusion

A coefficient fails divisibility by \(10\) exactly when it escapes \(2\) or escapes \(5\). By inclusion–exclusion for two sets, [6] the number of shifts escaping at least one prime is \(A_2+A_5-A_{2,5}\), so

$$T(m,n)=L-A_2-A_5+A_{2,5}.$$

The length \(L=m-n\) is already known, so only the three carry-free counts remain.

7. A worked example: \(T(20,3)\)

Take \(n=3\) and \(m=20\), so \(L=17\). The coefficients \(\binom{3+x}{3}\) for \(x=0,1,\dots,16\) are \(1\), \(4\), \(10\), \(20\), \(35\), \(56\), \(84\), \(120\), \(165\), \(220\), \(286\), \(364\), \(455\), \(560\), \(680\), \(816\) and \(969\). Six are multiples of \(10\), at \(x=2,3,7,9,13,14\), so \(T(20,3)=6\).

Now the three counts. In binary \(n=11_2\), so \(x\mathbin{\&}3=0\) forces the bottom two bits of \(x\) to vanish, giving \(x\in\{0,4,8,12,16\}\) and \(A_2=5\). In base \(5\), \(n=3\), so the units digit is capped at \(4-3=1\) and the higher digits are free; this gives \(x\in\{0,1,5,6,10,11,15,16\}\) and \(A_5=8\). The two lists overlap in \(\{0,16\}\), so \(A_{2,5}=2\), and

$$17-5-8+2=6,$$

the overlap restoring the two shifts subtracted twice. For the cost only \(A_5\) matters: those candidates get enumerated one by one, whereas \(A_2\) is counted from its digit restrictions without listing a shift.

8. Counting one prime by scanning digits

Counting \(A_p\) means counting the \(x\) with \(0\le x\le M\), \(M=L-1\), whose base-\(p\) digits obey \(x_j\le a_j\) for \(a_j=p-1-n_j\). Pad the digits of \(n\) and of \(M\) to a common length and scan from the top position down to the units. A partial choice of the top digits is either tight, agreeing with \(M\) everywhere scanned so far, so later digits are still bounded by \(M\), or loose, already below \(M\), so later digits are free up to their caps. Let \(t\) and \(l\) count them; the empty choice is tight, so \(t=1\), \(l=0\). At position \(j\), with cap \(a_j\) and bound digit \(M_j\),

$$l'=(a_j+1)\,l+\min(a_j+1,M_j)\,t,$$

together with \(t'=t\) when \(M_j\le a_j\) and \(t'=0\) otherwise: a loose choice admits every digit \(0,\dots,a_j\), while a tight one admits the \(\min(a_j+1,M_j)\) digits below \(M_j\) and turns loose, staying tight only by taking \(M_j\), which the cap permits exactly when \(M_j\le a_j\). After the units digit, \(A_p=t+l\).

For the base-\(5\) count of Section 7, \(M=16=31_5\) with caps \(4\) above and \(1\) at the units. The upper digit sends \((t,l)=(1,0)\) to \((1,3)\), as \(0,1,2\) go loose and \(3\) stays tight; the units digit gives \(l'=2\cdot3+1=7\) and \(t'=1\), so \(A_5=8\) as found by hand. The published programs loop over the tight-prefix digits rather than use these closed forms, at most \(p\) per position.

A special case is a useful check. If \(L=p^k\), the bottom \(k\) digits of \(M=p^k-1\) all equal \(p-1\), so every combination of those digits obeying the caps already lies below \(L\), while the higher digits of \(x\) must vanish; the count is the free product \(\prod_{j\lt k}(p-n_j)\). The tight state itself usually dies here, as soon as a bound digit exceeds its cap, and the product does not depend on it. For \(p=2\) and \(2^k\gt n\) the product is \(2^{k-s_2(n)}\), \(s_2(n)\) being the number of \(1\)-bits of \(n\): the fixed-column mirror of Glaisher's count of odd entries in a row of Pascal's triangle. [5]

9. Why base 5 is cheap for this \(n\)

The choice \(n=10^{12}-10\) is not innocent. Because \(5^{12}\) divides \(10^{12}\), we have \(n\equiv-10\pmod{5^{12}}\), so the bottom twelve base-\(5\) digits of \(n\) are those of

$$5^{12}-10=4\cdot5^{11}+\dots+4\cdot5^{2}+3\cdot5,$$

namely \(0,3,4,4,4,4,4,4,4,4,4,4\) from the units up. In all, \(n\) has \(18\) base-\(5\) digits and \(11\) equal \(4\); each of those caps the matching digit of \(x\) at \(0\), so eleven of the twenty-six positions give no freedom at all. Base-\(5\) carry-free shifts are therefore rare: \(1258291200\) below \(L\), about \(1.3\cdot10^{9}\) out of \(10^{18}\). The checkpoint is built the same way, \(10^7-10\) having digits \(0,3,4,4,4,4,4,2,0,0,1\) with five fours.

10. Counting the overlap by filtered enumeration

The scan of Section 8 tests digit caps in one chosen base and keeps no state for the other, so it cannot count \(A_{2,5}\); nor do the two digit systems line up, and for a general \(n\) we know of no small joint state. We therefore enumerate one carry-free set outright and test each member against the other condition. Since the base-\(2\) test is a single and, we enumerate the base-\(5\) set and count the members with \(x\mathbin{\&}n=0\). The work is then proportional to \(A_5\), and the base matters: at the target \(A_5\) is about \(1.3\cdot10^{9}\), the base-\(2\) count larger by over two orders of magnitude.

11. Splitting the base-5 digits into two blocks

A single recursion over the base-\(5\) digits could test each shift as it is completed, keeping only its traversal state. The published programs instead store two lists and form the shifts as sums \(x=\ell+5^{s}h\) of a low and a scaled high part. The split point \(s\) is the largest index with

$$M_j=a_j\qquad(j\lt s),$$

the longest run of positions, from the units up, where the digit of \(M=L-1\) already equals the cap. That choice buys the lemma which makes the two blocks independent.

Lemma. Let \(\ell\) be any value of the bottom \(s\) digits obeying the caps, let \(h\ge0\), and put \(x=\ell+5^{s}h\). Then \(x\le M\) if and only if \(h\le\lfloor M/5^{s}\rfloor\).

Proof. The largest admissible low part is \(\ell_{\max}=\sum_{j\lt s}a_j5^{j}\), and by the choice of \(s\)

$$\ell_{\max}=\sum_{j\lt s}M_j5^{j}=M\bmod 5^{s}.$$

So \(h\le\lfloor M/5^{s}\rfloor\) gives

$$x\le \ell_{\max}+5^{s}\lfloor M/5^{s}\rfloor=M,$$

while \(h\gt\lfloor M/5^{s}\rfloor\) gives \(x\ge 5^{s}h\gt M\). \(\square\)

The low block is therefore a free Cartesian product of \(\prod_{j\lt s}(a_j+1)\) combinations. Only the high block still feels the bound, and the tight-or-loose walk of Section 8 enumerates it, now listing values rather than counting them. The two lengths multiply to exactly \(A_5\), and the memory is their sum. Nothing guarantees both lists are short: when \(s=0\) the low list is \(\{0\}\) and the high list holds every candidate. Here the split is deep and both stay small.

Section 7 in miniature: \(M=16\) has digits \(1,3\) and caps \(a_0=1\), \(a_1=4\), so \(M_0=a_0\) while \(M_1\ne a_1\) and \(s=1\). The low list \(\{0,1\}\) and the scaled high list \(\{0,5,10,15\}\) give the eight sums \(0,1,5,6,10,11,15,16\), exactly the carry-free shifts found by hand, with \(2\cdot4=8=A_5\); those meeting \(x\mathbin{\&}3=0\) are \(0\) and \(16\), so \(A_{2,5}=2\).

12. The checkpoint, fully worked

For \(m=10^9\) and \(n=10^7-10\) the interval is \(L=990000010\), and the digit scan returns \(A_2=241984\), \(A_5=61050\). The split is \(s=9\) with low-block caps \(4,1,0,0,0,0,0,2,4\), so the low list has \(5\cdot2\cdot3\cdot5=150\) members; the high bound is \(506\) and the high list \(407\), and \(150\cdot407=61050=A_5\) as the lemma predicts. Of those only \(24\) also satisfy \(x\mathbin{\&}n=0\), so \(A_{2,5}=24\) and

$$990000010-241984-61050+24,$$

which is \(989697000\), the value given in the statement. At the target the same construction yields \(s=18\), a low list of \(4800\) members and a high list of \(262144\), whose product is the \(1258291200\) shifts of Section 9.

How the Code Works

C++ enters at main, which parses its options, runs the enabled checkpoints and calls solve_T; Python and Java reach that routine through solve. Identifiers below are the C++ spellings: Python writes length for len, Java toBaseDigits, countNoCarryPrime and the like. to_base_digits returns a number's digits least significant first, giving the single digit \(0\) for input \(0\) so that digit lists are never empty; the scale \(5^{s}\) comes from pow_u64 in C++, 5 ** split in Python and a multiplication loop in Java.

count_no_carry_prime(n, limit, p) is Section 8. It sets max_value to limit \(-\,1\), pads the digit lists of \(n\) and of that bound to a common length, and initialises tight and loose to \(1\) and \(0\). Running pos downwards, it sets allowed_max to \(p-1-n_{\mathrm{pos}}\) and sends each tight prefix to next_loose for a digit below bound_digit or to next_tight for one equal to it, multiplying every loose prefix by allowed_max + 1. The returned tight + loose is \(A_p\); its guard allowed_max >= 0 can never fail, a base-\(p\) digit being at most \(p-1\).

generate_low_values and its helper generate_low_values_rec build the low list, recursing over positions \(0,\dots,s-1\) and trying every digit from \(0\) up to the cap while carrying the running value and the place value \(5^{\mathrm{pos}}\). No bound is consulted, which is what the lemma licenses.

generate_high_values builds the high list. It takes the digits of high_bound, copies the caps of the global positions i + split into high_allowed, precomputes pow5, and walks the digits downwards with an explicit stack of states holding pos, a tight flag and the partial value. The digit at digit_index = len - 1 - pos may reach min(limit_digit, allowed_digit), where limit_digit is the bound digit while tight and \(4\) once loose; a state stays tight only by taking limit_digit, and completed states join the output. Note that this stack also holds pending siblings, at most four per level, so a high block of \(d\) digits needs up to \(1+4d\) entries: at the target \(d=8\) and it peaks at \(32\), while the low-list recursion is only \(s+1=19\) frames deep.

count_overlap_coprime_10 assembles the overlap: it forms allowed_digits as \(4-n_j\), finds split by advancing while the bound digit equals the cap, sets step to \(5^{\mathrm{split}}\) and high_bound to max_value / step, builds the two lists and premultiplies the high one into scaled_high_values, so the counting loop costs one addition and one and per pair. That loop walks ranges of the low list, counting pairs with (x & n) == 0; the per-range subtotals are added at the end, and the total is independent of how the ranges are cut, every pair being visited once.

solve_T is Section 6 verbatim: it calls the digit scan for \(p=2\) and \(p=5\), then the overlap routine, and returns interval - not_divisible_by_2 - not_divisible_by_5 + coprime_to_10.

The C++ file alone carries the self-checks: v_p_factorial evaluates Legendre's formula, brute_force_T uses it for \(v_2\) and \(v_5\) of each coefficient over a short interval, and run_checkpoints compares the fast method with the statement's value at \(T(10^9,10^7-10)\) and with that brute force at \(T(100,10)\), \(T(160,47)\) and \(T(220,90)\), aborting if any disagrees. parse_arguments and parse_u64_after_prefix read the command line, where --skip-checkpoints suppresses that stage. [7]

The three languages differ mainly in integer handling. C++ works in std::uint64_t, unsigned and exactly \(64\) bits wide; [8] every value stays below \(2^{63}\), and \(A_2+A_5\le L\) at the checkpoint and the target, so the subtraction in solve_T does not wrap. For tiny inputs it can: at \(m=2\), \(n=1\) all three counts are \(1\) and \(1-1-1\) wraps, yet the answer is still right, unsigned arithmetic being exact modulo \(2^{64}\) with the true count in \([0,L]\). Java's signed long has maximum \(2^{63}-1\gt L\), and it compares boxed digits with equals rather than == when searching for the split, the right choice for Integer objects; Python's integers are unbounded. Two behavioural differences remain: Java's solve returns Error instead of propagating a failure, and neither the Python nor the Java file runs a checkpoint.

Complexity and Verification

The two digit scans are negligible. With \(D_p\) the larger of the base-\(p\) digit lengths of \(n\) and of \(M\), and at most \(p\) operations per position, they cost \(O(2D_2+5D_5)\) fixed-width operations; at the target \(D_2=60\), \(D_5=26\), a few hundred in all. Building the lists is \(O(D_5(B+H))\) for list lengths \(B\) and \(H\), since the generators pass through internal nodes as well as finished values: the target's \(4800\) and \(262144\) values cost \(6616\) recursive calls and \(327683\) visited states, plus \(H\) multiplications to scale the high list.

The overlap dominates. It performs one addition and one bitwise and for each of the \(4800\cdot262144=1258291200\) pairs, so its time is \(\Theta(A_5)\); its memory is the low, high and scaled high lists, \(4800+2\cdot262144\) entries, which in C++ occupy \(4232704\) bytes of \(64\)-bit elements, about \(4.2\) MB, before spare vector capacity. Java boxes those entries as Long objects and Python stores references to integers, so neither port matches that figure. Measured on one \(2026\) laptop, a sequential sweep over all \(1.26\cdot10^{9}\) pairs, checkpoints included, took about \(0.16\) s for the C++ file built with -O2, against some \(4.3\) s for the Java port and about \(45\) s of interpreted work for Python.

It should be said plainly that \(\Theta(A_5)\) is no bound in \(\log L\). The method is fast here only because \(n=10^{12}-10\) has eleven base-\(5\) digits equal to \(4\); for an \(n\) with mostly small base-\(5\) digits, \(A_5\) grows towards \(L\) and the enumeration is hopeless, though the two scans stay cheap. The memory is likewise proportional to \(B+H\), not the digit count.

Beyond the checkpoints packaged in the C++ file, the following were checked for this article by running code.

  • Kummer's carry count agrees with \(v_p\) of the coefficient for \(p=2,3,5,7\) and all \(n,x\lt40\): \(6400\) cases. The identity \(S_j=N_j+X_j+c_j\) holds in \(27000\) random instances of \(p\), \(n\), \(x\) and \(j\).
  • The digit scan agrees with direct enumeration in both bases, and the split enumeration with direct enumeration of the overlap, over \(120\) random pairs \((n,L)\) with \(L\le4000\); the closed forms of Section 8 agree with the code's explicit digit loop in \(4000\) random cases, and the free product \(\prod_{j\lt k}(p-n_j)\) with its binary form \(2^{k-s_2(n)}\) in \(2400\).
  • For the lemma: the largest admissible low part equals \(M\bmod 5^{s}\) in \(500\) random cases, and the two list lengths multiply to \(A_5\) in \(400\).
  • The method matches a brute force over \(v_2\) and \(v_5\) of every coefficient for all \(1\le n\le60\), \(n\lt m\le n+120\) — \(7200\) values of \(T(m,n)\) — and for \(1414\) further pairs \((n,L)\) next to powers of \(2\), \(5\) and \(10\), where the split is least predictable.
  • The decomposition of Section 12 reproduces the published checkpoint exactly, and the C++, Python and Java programs return byte-identical output at the target.

Footnotes and References

The references below supply the background facts used above. The carry identity of Section 3, the digit-scan recurrence and the splitting lemma are derived explicitly in this article.

  1. Project Euler 322 — Binomial Coefficients Divisible by 10. The official statement defines T(m, n) as the number of binomial coefficients C(i, n) divisible by 10 for n ≤ i < m, with i, m and n positive integers, gives T(10⁹, 10⁷ − 10) = 989697000, and asks for T(10¹⁸, 10¹² − 10).
  2. Wikipedia — Kummer's theorem. For a prime p the p-adic valuation of C(n, m) equals the number of carries when m is added to n − m in base p, equivalently (s_p(m) + s_p(n − m) − s_p(n))/(p − 1) in base-p digit sums. It credits Ernst Kummer, 1852, and notes that the result follows from Legendre's formula — the route of Sections 3 and 4.
  3. Wikipedia — Legendre's formula. The exponent of a prime p in n! equals the sum of ⌊n/pⁱ⌋ over i ≥ 1, finite because the terms vanish once pⁱ exceeds n, and equals (n − s_p(n))/(p − 1) in digit-sum form. Its worked example is v₂(6!) = 3 + 1 = 4, used in Section 3, and its applications section records that the formula proves Kummer's theorem.
  4. Wikipedia — Lucas's theorem. For a prime p, C(m, n) is congruent modulo p to the product of C(m_i, n_i) over the base-p digits, so p divides C(m, n) exactly when some digit of n exceeds the matching digit of m; in binary, C(m, n) is odd exactly when the 1-bits of n lie among those of m. It credits Édouard Lucas, 1878 — the digit condition of Section 4 from the other side.
  5. OEIS A001316 — Gould's sequence. The number of odd entries in row n of Pascal's triangle: terms 1, 2, 2, 4, 2, 4, 4, 8, 2, 4, 4, 8, … and the closed form 2^A000120(n), where A000120 counts the 1-bits of n; a comment notes that James Glaisher proved the count in 1899. Section 8 derives the fixed-column mirror 2^(k − s₂(n)).
  6. Wikipedia — Inclusion–exclusion principle. The page opens with the two-set case |A ∪ B| = |A| + |B| − |A ∩ B|, the form used in Section 6.
  7. C++ — Euler322.cpp. The linked immutable C++ revision contains count_no_carry_prime, generate_low_values, generate_high_values, count_overlap_coprime_10, solve_T, brute_force_T and run_checkpoints; it is the implementation record for this article. The extra comparisons above are additional checks, not tests in that file.
  8. cppreference — fixed width integer types records that std::uint64_t, where an implementation provides it, is unsigned with exactly 64 bits and no padding bits. Java — Long gives the signed range −2⁶³ to 2⁶³ − 1 and documents the boxed Long objects, and Python — numeric types states that integers have unlimited precision.

Problem 322 source code

C++

#include <algorithm>
#include <cstdint>
#include <iostream>
#include <limits>
#include <string>
#include <thread>
#include <vector>

namespace {

using u64 = std::uint64_t;

struct Options {
    bool run_checkpoints = true;
    bool allow_multithreading = true;
    unsigned requested_threads = 0U;
};

std::vector<int> to_base_digits(u64 x, const int base) {
    std::vector<int> digits;
    if (x == 0ULL) {
        digits.push_back(0);
        return digits;
    }

    while (x > 0ULL) {
        digits.push_back(static_cast<int>(x % static_cast<u64>(base)));
        x /= static_cast<u64>(base);
    }
    return digits;
}

u64 pow_u64(u64 base, int exponent) {
    u64 result = 1ULL;
    while (exponent-- > 0) {
        result *= base;
    }
    return result;
}

bool parse_u64_after_prefix(const std::string& arg, const char* prefix, u64& value) {
    const std::string p(prefix);
    if (arg.rfind(p, 0) != 0U) {
        return false;
    }

    const std::string tail = arg.substr(p.size());
    if (tail.empty()) {
        return false;
    }

    u64 parsed = 0ULL;
    for (const char c : tail) {
        if (c < '0' || c > '9') {
            return false;
        }
        const u64 digit = static_cast<u64>(c - '0');
        if (parsed > (std::numeric_limits<u64>::max() - digit) / 10ULL) {
            return false;
        }
        parsed = parsed * 10ULL + digit;
    }

    value = parsed;
    return true;
}

bool parse_arguments(const int argc, char** argv, Options& options) {
    for (int i = 1; i < argc; ++i) {
        const std::string arg(argv[i]);

        if (arg == "--skip-checkpoints") {
            options.run_checkpoints = false;
            continue;
        }
        if (arg == "--single-thread") {
            options.allow_multithreading = false;
            continue;
        }

        u64 parsed = 0ULL;
        if (parse_u64_after_prefix(arg, "--threads=", parsed)) {
            if (parsed > static_cast<u64>(std::numeric_limits<unsigned>::max())) {
                std::cerr << "--threads is too large.\n";
                return false;
            }
            options.requested_threads = static_cast<unsigned>(parsed);
            continue;
        }

        std::cerr << "Unknown argument: " << arg << '\n';
        return false;
    }
    return true;
}

unsigned choose_thread_count(const bool allow_multithreading,
                             const unsigned requested_threads,
                             const std::size_t workload_units) {
    if (!allow_multithreading || workload_units < 2ULL) {
        return 1U;
    }

    unsigned threads = requested_threads;
    if (threads == 0U) {
        threads = std::thread::hardware_concurrency();
        if (threads == 0U) {
            threads = 1U;
        }
    }

    return std::max(1U, std::min<unsigned>(threads, static_cast<unsigned>(workload_units)));
}

u64 count_no_carry_prime(const u64 n, const u64 limit, const int p) {
    // Counts x in [0, limit) such that adding n and x in base p has no carries.
    if (limit == 0ULL) {
        return 0ULL;
    }

    const u64 max_value = limit - 1ULL;
    std::vector<int> n_digits = to_base_digits(n, p);
    std::vector<int> x_digits = to_base_digits(max_value, p);

    const int len = std::max<int>(n_digits.size(), x_digits.size());
    n_digits.resize(len, 0);
    x_digits.resize(len, 0);

    u64 tight = 1ULL;
    u64 loose = 0ULL;

    for (int pos = len - 1; pos >= 0; --pos) {
        const int allowed_max = p - 1 - n_digits[pos];
        const int bound_digit = x_digits[pos];

        u64 next_tight = 0ULL;
        u64 next_loose = 0ULL;

        if (allowed_max >= 0) {
            next_loose += loose * static_cast<u64>(allowed_max + 1);

            for (int d = 0; d <= std::min(allowed_max, bound_digit); ++d) {
                if (d < bound_digit) {
                    next_loose += tight;
                } else {
                    next_tight += tight;
                }
            }
        }

        tight = next_tight;
        loose = next_loose;
    }

    return tight + loose;
}

void generate_low_values_rec(const std::vector<int>& allowed_digits,
                             const int pos,
                             const int split,
                             const u64 current,
                             const u64 place_value,
                             std::vector<u64>& out) {
    if (pos == split) {
        out.push_back(current);
        return;
    }

    for (int d = 0; d <= allowed_digits[pos]; ++d) {
        generate_low_values_rec(
            allowed_digits, pos + 1, split, current + static_cast<u64>(d) * place_value, place_value * 5ULL, out);
    }
}

std::vector<u64> generate_low_values(const std::vector<int>& allowed_digits, const int split) {
    std::vector<u64> out;
    generate_low_values_rec(allowed_digits, 0, split, 0ULL, 1ULL, out);
    return out;
}

std::vector<u64> generate_high_values(const std::vector<int>& allowed_digits,
                                      const int split,
                                      const u64 high_bound) {
    std::vector<int> bound_digits = to_base_digits(high_bound, 5);
    const int len = static_cast<int>(bound_digits.size());

    std::vector<int> high_allowed(len, 4);
    for (int i = 0; i < len; ++i) {
        const int original_pos = i + split;
        if (original_pos < static_cast<int>(allowed_digits.size())) {
            high_allowed[i] = allowed_digits[original_pos];
        }
    }

    std::vector<u64> pow5(len + 1, 1ULL);
    for (int i = 1; i <= len; ++i) {
        pow5[i] = pow5[i - 1] * 5ULL;
    }

    struct Node {
        int pos = 0;
        bool tight = true;
        u64 value = 0ULL;
    };

    std::vector<Node> stack;
    stack.push_back(Node());

    std::vector<u64> out;
    while (!stack.empty()) {
        const Node node = stack.back();
        stack.pop_back();

        if (node.pos == len) {
            out.push_back(node.value);
            continue;
        }

        const int digit_index = len - 1 - node.pos;
        const int limit_digit = node.tight ? bound_digits[digit_index] : 4;
        const int allowed_digit = high_allowed[digit_index];
        const int max_digit = std::min(limit_digit, allowed_digit);

        for (int d = max_digit; d >= 0; --d) {
            Node next;
            next.pos = node.pos + 1;
            next.tight = node.tight && (d == limit_digit);
            next.value = node.value + static_cast<u64>(d) * pow5[digit_index];
            stack.push_back(next);
        }
    }

    return out;
}

u64 count_overlap_coprime_10(const u64 n,
                             const u64 limit,
                             const bool allow_multithreading,
                             const unsigned requested_threads) {
    // Counts x in [0, limit) such that C(n+x, n) is coprime to 10.
    if (limit == 0ULL) {
        return 0ULL;
    }

    const u64 max_value = limit - 1ULL;
    std::vector<int> n_digits = to_base_digits(n, 5);
    std::vector<int> x_digits = to_base_digits(max_value, 5);
    const int len = std::max<int>(n_digits.size(), x_digits.size());
    n_digits.resize(len, 0);
    x_digits.resize(len, 0);

    std::vector<int> allowed_digits(len, 4);
    for (int i = 0; i < len; ++i) {
        allowed_digits[i] = 4 - n_digits[i];
    }

    int split = 0;
    while (split < len && x_digits[split] == allowed_digits[split]) {
        ++split;
    }

    const u64 step = pow_u64(5ULL, split);
    const u64 high_bound = max_value / step;

    const std::vector<u64> low_values = generate_low_values(allowed_digits, split);
    const std::vector<u64> high_values = generate_high_values(allowed_digits, split, high_bound);

    std::vector<u64> scaled_high_values;
    scaled_high_values.reserve(high_values.size());
    for (const u64 h : high_values) {
        scaled_high_values.push_back(h * step);
    }

    const unsigned threads = choose_thread_count(allow_multithreading, requested_threads, low_values.size());
    std::vector<std::thread> workers;
    workers.reserve(threads);
    std::vector<u64> partial(threads, 0ULL);

    auto worker = [&](const unsigned tid) {
        const std::size_t begin = (low_values.size() * tid) / threads;
        const std::size_t end = (low_values.size() * (tid + 1U)) / threads;

        u64 local = 0ULL;
        for (std::size_t i = begin; i < end; ++i) {
            const u64 low = low_values[i];
            for (const u64 scaled : scaled_high_values) {
                const u64 x = low + scaled;
                if ((x & n) == 0ULL) {
                    ++local;
                }
            }
        }
        partial[tid] = local;
    };

    for (unsigned tid = 0; tid < threads; ++tid) {
        workers.emplace_back(worker, tid);
    }
    for (std::thread& worker_thread : workers) {
        worker_thread.join();
    }

    u64 total = 0ULL;
    for (const u64 value : partial) {
        total += value;
    }
    return total;
}

u64 solve_T(const u64 m,
            const u64 n,
            const bool allow_multithreading,
            const unsigned requested_threads) {
    const u64 interval = m - n;

    const u64 not_divisible_by_2 = count_no_carry_prime(n, interval, 2);
    const u64 not_divisible_by_5 = count_no_carry_prime(n, interval, 5);
    const u64 coprime_to_10 =
        count_overlap_coprime_10(n, interval, allow_multithreading, requested_threads);

    return interval - not_divisible_by_2 - not_divisible_by_5 + coprime_to_10;
}

u64 v_p_factorial(u64 x, const int p) {
    u64 total = 0ULL;
    while (x > 0ULL) {
        x /= static_cast<u64>(p);
        total += x;
    }
    return total;
}

u64 brute_force_T(const u64 m, const u64 n) {
    u64 count = 0ULL;
    for (u64 i = n; i < m; ++i) {
        const u64 vp2 =
            v_p_factorial(i, 2) - v_p_factorial(n, 2) - v_p_factorial(i - n, 2);
        const u64 vp5 =
            v_p_factorial(i, 5) - v_p_factorial(n, 5) - v_p_factorial(i - n, 5);
        if (vp2 > 0ULL && vp5 > 0ULL) {
            ++count;
        }
    }
    return count;
}

bool run_checkpoints(const Options& options) {
    struct Case {
        u64 m;
        u64 n;
        u64 expected;
    };

    const std::vector<Case> known_cases = {
        {1000000000ULL, 10000000ULL - 10ULL, 989697000ULL},
    };

    for (const Case& c : known_cases) {
        const u64 got = solve_T(c.m, c.n, options.allow_multithreading, options.requested_threads);
        if (got != c.expected) {
            std::cerr << "Checkpoint failed for T(" << c.m << ", " << c.n << "): got " << got
                      << ", expected " << c.expected << '\n';
            return false;
        }
    }

    const std::vector<std::pair<u64, u64>> brute_cases = {
        {100ULL, 10ULL},
        {160ULL, 47ULL},
        {220ULL, 90ULL},
    };

    for (const auto& c : brute_cases) {
        const u64 fast = solve_T(c.first, c.second, false, 1U);
        const u64 brute = brute_force_T(c.first, c.second);
        if (fast != brute) {
            std::cerr << "Brute checkpoint failed for T(" << c.first << ", " << c.second
                      << "): fast=" << fast << ", brute=" << brute << '\n';
            return false;
        }
    }

    return true;
}

}  // namespace

int main(int argc, char** argv) {
    Options options;
    if (!parse_arguments(argc, argv, options)) {
        return 1;
    }

    if (options.run_checkpoints && !run_checkpoints(options)) {
        return 1;
    }

    const u64 m = 1000000000000000000ULL;
    const u64 n = 1000000000000ULL - 10ULL;
    const u64 answer = solve_T(m, n, options.allow_multithreading, options.requested_threads);
    std::cout << answer << '\n';
    return 0;
}

Python

import math
from multiprocessing import Pool, cpu_count

def to_base_digits(x, base):
    if x == 0:
        return [0]
    digits = []
    while x > 0:
        digits.append(x % base)
        x //= base
    return digits

def count_no_carry_prime(n, limit, p):
    if limit == 0:
        return 0
    max_value = limit - 1
    n_digits = to_base_digits(n, p)
    x_digits = to_base_digits(max_value, p)
    
    length = max(len(n_digits), len(x_digits))
    n_digits += [0] * (length - len(n_digits))
    x_digits += [0] * (length - len(x_digits))
    
    tight = 1
    loose = 0
    
    for pos in range(length - 1, -1, -1):
        allowed_max = p - 1 - n_digits[pos]
        bound_digit = x_digits[pos]
        
        next_tight = 0
        next_loose = 0
        
        if allowed_max >= 0:
            next_loose += loose * (allowed_max + 1)
            for d in range(min(allowed_max, bound_digit) + 1):
                if d < bound_digit:
                    next_loose += tight
                else:
                    next_tight += tight
                    
        tight = next_tight
        loose = next_loose
        
    return tight + loose

def generate_low_values_rec(allowed_digits, pos, split, current, place_value, out):
    if pos == split:
        out.append(current)
        return
    for d in range(allowed_digits[pos] + 1):
        generate_low_values_rec(allowed_digits, pos + 1, split, current + d * place_value, place_value * 5, out)

def generate_low_values(allowed_digits, split):
    out = []
    generate_low_values_rec(allowed_digits, 0, split, 0, 1, out)
    return out

def generate_high_values(allowed_digits, split, high_bound):
    bound_digits = to_base_digits(high_bound, 5)
    length = len(bound_digits)
    
    high_allowed = [4] * length
    for i in range(length):
        original_pos = i + split
        if original_pos < len(allowed_digits):
            high_allowed[i] = allowed_digits[original_pos]
            
    pow5 = [1] * (length + 1)
    for i in range(1, length + 1):
        pow5[i] = pow5[i - 1] * 5
        
    stack = [(0, True, 0)]
    out = []
    
    while stack:
        pos, tight, value = stack.pop()
        
        if pos == length:
            out.append(value)
            continue
            
        digit_index = length - 1 - pos
        limit_digit = bound_digits[digit_index] if tight else 4
        allowed_digit = high_allowed[digit_index]
        max_digit = min(limit_digit, allowed_digit)
        
        for d in range(max_digit, -1, -1):
            next_tight = tight and (d == limit_digit)
            next_value = value + d * pow5[digit_index]
            stack.append((pos + 1, next_tight, next_value))
            
    return out

def worker(args):
    lows, scaled_highs, n = args
    local = 0
    for low in lows:
        for scaled in scaled_highs:
            x = low + scaled
            if (x & n) == 0:
                local += 1
    return local

def count_overlap_coprime_10(n, limit):
    if limit == 0:
        return 0
    max_value = limit - 1
    n_digits = to_base_digits(n, 5)
    x_digits = to_base_digits(max_value, 5)
    
    length = max(len(n_digits), len(x_digits))
    n_digits += [0] * (length - len(n_digits))
    x_digits += [0] * (length - len(x_digits))
    
    allowed_digits = [4 - d for d in n_digits]
    
    split = 0
    while split < length and x_digits[split] == allowed_digits[split]:
        split += 1
        
    step = 5 ** split
    high_bound = max_value // step
    
    low_values = generate_low_values(allowed_digits, split)
    high_values = generate_high_values(allowed_digits, split, high_bound)
    
    scaled_high_values = [h * step for h in high_values]
    
    threads = cpu_count()
    chunk_size = max(1, len(low_values) // threads)
    chunks = []
    for i in range(0, len(low_values), chunk_size):
        chunks.append((low_values[i:i + chunk_size], scaled_high_values, n))
        
    with Pool(threads) as pool:
        results = pool.map(worker, chunks)
        
    return sum(results)

def solve_T(m, n):
    interval = m - n
    not_div_2 = count_no_carry_prime(n, interval, 2)
    not_div_5 = count_no_carry_prime(n, interval, 5)
    coprime_10 = count_overlap_coprime_10(n, interval)
    
    return interval - not_div_2 - not_div_5 + coprime_10

def solve():
    m = 1000000000000000000
    n = 1000000000000 - 10
    return str(solve_T(m, n))

if __name__ == '__main__':
    print(solve())

Java

import java.util.*;
import java.util.concurrent.*;

public class Euler322 {
    static List<Integer> toBaseDigits(long x, int base) {
        List<Integer> digits = new ArrayList<>();
        if (x == 0) {
            digits.add(0);
            return digits;
        }
        while (x > 0) {
            digits.add((int) (x % base));
            x /= base;
        }
        return digits;
    }

    static long countNoCarryPrime(long n, long limit, int p) {
        if (limit == 0)
            return 0;
        long maxValue = limit - 1;
        List<Integer> nDigits = toBaseDigits(n, p);
        List<Integer> xDigits = toBaseDigits(maxValue, p);

        int len = Math.max(nDigits.size(), xDigits.size());
        while (nDigits.size() < len)
            nDigits.add(0);
        while (xDigits.size() < len)
            xDigits.add(0);

        long tight = 1;
        long loose = 0;

        for (int pos = len - 1; pos >= 0; --pos) {
            int allowedMax = p - 1 - nDigits.get(pos);
            int boundDigit = xDigits.get(pos);

            long nextTight = 0;
            long nextLoose = 0;

            if (allowedMax >= 0) {
                nextLoose += loose * (allowedMax + 1);
                for (int d = 0; d <= Math.min(allowedMax, boundDigit); ++d) {
                    if (d < boundDigit) {
                        nextLoose += tight;
                    } else {
                        nextTight += tight;
                    }
                }
            }
            tight = nextTight;
            loose = nextLoose;
        }
        return tight + loose;
    }

    static void generateLowValuesRec(List<Integer> allowedDigits, int pos, int split, long current, long placeValue,
            List<Long> out) {
        if (pos == split) {
            out.add(current);
            return;
        }
        for (int d = 0; d <= allowedDigits.get(pos); ++d) {
            generateLowValuesRec(allowedDigits, pos + 1, split, current + d * placeValue, placeValue * 5L, out);
        }
    }

    static List<Long> generateLowValues(List<Integer> allowedDigits, int split) {
        List<Long> out = new ArrayList<>();
        generateLowValuesRec(allowedDigits, 0, split, 0L, 1L, out);
        return out;
    }

    static class Node {
        int pos;
        boolean tight;
        long value;

        Node(int p, boolean t, long v) {
            pos = p;
            tight = t;
            value = v;
        }
    }

    static List<Long> generateHighValues(List<Integer> allowedDigits, int split, long highBound) {
        List<Integer> boundDigits = toBaseDigits(highBound, 5);
        int len = boundDigits.size();

        List<Integer> highAllowed = new ArrayList<>(Collections.nCopies(len, 4));
        for (int i = 0; i < len; ++i) {
            int originalPos = i + split;
            if (originalPos < allowedDigits.size()) {
                highAllowed.set(i, allowedDigits.get(originalPos));
            }
        }

        long[] pow5 = new long[len + 1];
        pow5[0] = 1L;
        for (int i = 1; i <= len; ++i)
            pow5[i] = pow5[i - 1] * 5L;

        List<Node> stack = new ArrayList<>();
        stack.add(new Node(0, true, 0L));

        List<Long> out = new ArrayList<>();

        while (!stack.isEmpty()) {
            Node node = stack.remove(stack.size() - 1);
            if (node.pos == len) {
                out.add(node.value);
                continue;
            }

            int digitIndex = len - 1 - node.pos;
            int limitDigit = node.tight ? boundDigits.get(digitIndex) : 4;
            int allowedDigit = highAllowed.get(digitIndex);
            int maxDigit = Math.min(limitDigit, allowedDigit);

            for (int d = maxDigit; d >= 0; --d) {
                stack.add(new Node(
                        node.pos + 1,
                        node.tight && (d == limitDigit),
                        node.value + d * pow5[digitIndex]));
            }
        }
        return out;
    }

    static class Worker implements Callable<Long> {
        List<Long> lowValues;
        List<Long> scaledHighValues;
        long n;
        int start, end;

        Worker(List<Long> lows, List<Long> highs, long n, int start, int end) {
            this.lowValues = lows;
            this.scaledHighValues = highs;
            this.n = n;
            this.start = start;
            this.end = end;
        }

        public Long call() {
            long local = 0;
            for (int i = start; i < end; i++) {
                long low = lowValues.get(i);
                for (long scaled : scaledHighValues) {
                    long x = low + scaled;
                    if ((x & n) == 0) {
                        local++;
                    }
                }
            }
            return local;
        }
    }

    static long countOverlapCoprime10(long n, long limit) throws Exception {
        if (limit == 0)
            return 0;
        long maxValue = limit - 1;
        List<Integer> nDigits = toBaseDigits(n, 5);
        List<Integer> xDigits = toBaseDigits(maxValue, 5);

        int len = Math.max(nDigits.size(), xDigits.size());
        while (nDigits.size() < len)
            nDigits.add(0);
        while (xDigits.size() < len)
            xDigits.add(0);

        List<Integer> allowedDigits = new ArrayList<>(Collections.nCopies(len, 4));
        for (int i = 0; i < len; ++i) {
            allowedDigits.set(i, 4 - nDigits.get(i));
        }

        int split = 0;
        while (split < len && xDigits.get(split).equals(allowedDigits.get(split))) {
            split++;
        }

        long step = 1;
        for (int i = 0; i < split; i++)
            step *= 5L;
        long highBound = maxValue / step;

        List<Long> lowValues = generateLowValues(allowedDigits, split);
        List<Long> highValues = generateHighValues(allowedDigits, split, highBound);

        List<Long> scaledHighValues = new ArrayList<>();
        for (long h : highValues)
            scaledHighValues.add(h * step);

        int threads = Runtime.getRuntime().availableProcessors();
        ExecutorService ex = Executors.newFixedThreadPool(threads);
        List<Future<Long>> futures = new ArrayList<>();

        int batchSize = Math.max(1, lowValues.size() / threads);
        for (int i = 0; i < lowValues.size(); i += batchSize) {
            int end = Math.min(lowValues.size(), i + batchSize);
            futures.add(ex.submit(new Worker(lowValues, scaledHighValues, n, i, end)));
        }

        long total = 0;
        for (Future<Long> f : futures) {
            total += f.get();
        }
        ex.shutdown();

        return total;
    }

    static long solveT(long m, long n) throws Exception {
        long interval = m - n;
        long notDiv2 = countNoCarryPrime(n, interval, 2);
        long notDiv5 = countNoCarryPrime(n, interval, 5);
        long coprime10 = countOverlapCoprime10(n, interval);

        return interval - notDiv2 - notDiv5 + coprime10;
    }

    public static String solve() {
        try {
            long m = 1000000000000000000L;
            long n = 1000000000000L - 10L;
            return String.valueOf(solveT(m, n));
        } catch (Exception e) {
            return "Error";
        }
    }

    public static void main(String[] args) {
        System.out.println(solve());
    }
}