All Euler problems
Project Euler

Top Dice

In how many ways can 20 twelve-sided dice (sides numbered 1 to 12) be rolled so that the top 10 values sum to 70? For reference: there are 1111 ways for 5 six-sided dice where the top 3 sum to 15.

Source sync Apr 19, 2026
Problem #0240
Level Level 09
Solved By 2,511
Languages C++, Python
Answer 7448717393364181966
Length 405 words
probabilitydynamic_programminglinear_algebra

Problem Statement

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

There are \(1111\) ways in which five \(6\)-sided dice (sides numbered \(1\) to \(6\)) can be rolled so that the top three sum to \(15\). Some examples are:

\(D_1,D_2,D_3,D_4,D_5 = 4,3,6,3,5\)

\(D_1,D_2,D_3,D_4,D_5 = 4,3,3,5,6\)

\(D_1,D_2,D_3,D_4,D_5 = 3,3,3,6,6\)

\(D_1,D_2,D_3,D_4,D_5 = 6,6,3,3,3\)

In how many ways can twenty \(12\)-sided dice (sides numbered \(1\) to \(12\)) be rolled so that the top ten sum to \(70\)?

Problem 240: Top Dice

Mathematical Foundation

Theorem (Threshold Decomposition). Let D=12D = 12, N=20N = 20, H=10H = 10 (number of “top” dice), S=70S = 70 (target sum). The number of ordered NN-tuples (d1,,dN){1,,D}N(d_1, \ldots, d_N) \in \{1, \ldots, D\}^N whose HH largest values sum to SS is

Answer=t=1Dj=1Hm=0NH[j+m1]R(Hj,t+1,D,Sjt)B(NHm,1,t1)N!(frequency product)\text{Answer} = \sum_{t=1}^{D} \sum_{j=1}^{H} \sum_{m=0}^{N-H} [j + m \geq 1] \cdot R(H-j, t+1, D, S - jt) \cdot B(N-H-m, 1, t-1) \cdot \frac{N!}{\text{(frequency product)}}

decomposed over the threshold value t=d(H)t = d_{(H)} (the HH-th largest value), jj = number of top dice equal to tt, and mm = number of bottom dice equal to tt.

Proof. Sort the values: d(1)d(2)d(N)d_{(1)} \geq d_{(2)} \geq \cdots \geq d_{(N)}. The HH-th largest value t=d(H)t = d_{(H)} satisfies 1tD1 \leq t \leq D. Among the top HH dice, exactly j1j \geq 1 have value tt, and the remaining HjH - j have values strictly greater than tt, summing to SjtS - jt. Among the bottom NHN - H dice, exactly m0m \geq 0 have value tt, and the remaining NHmN - H - m have values strictly less than tt.

For each valid (t,j,m)(t, j, m):

  • The HjH - j “above-threshold” top dice contribute values from {t+1,,D}\{t+1, \ldots, D\} summing to SjtS - jt. Let R(h,a,b,s)R(h, a, b, s) denote the number of multisets of hh values from {a,,b}\{a, \ldots, b\} summing to ss.
  • The NHmN - H - m “below-threshold” bottom dice contribute values from {1,,t1}\{1, \ldots, t-1\}.
  • There are j+mj + m dice with value exactly tt.

The number of ordered tuples for a given frequency vector (n1,,nD)(n_1, \ldots, n_D) is the multinomial coefficient N!/v=1Dnv!N! / \prod_{v=1}^{D} n_v!. Summing over all valid frequency vectors gives the total count. \square

Lemma (Stars-and-Bars with Bounds). The count R(h,a,b,s)R(h, a, b, s) of multisets of hh values from {a,,b}\{a, \ldots, b\} summing to ss can be computed via dynamic programming on the recurrence

R(h,a,b,s)=v=amin(b,s)R(h1,v,b,sv)R(h, a, b, s) = \sum_{v=a}^{\min(b, s)} R(h-1, v, b, s-v)

with base case R(0,a,b,0)=1R(0, a, b, 0) = 1 and R(0,a,b,s)=0R(0, a, b, s) = 0 for s>0s > 0.

Proof. Fix the smallest value in the multiset as v{a,,b}v \in \{a, \ldots, b\}, subtract it from the sum, and recurse on the remaining h1h-1 values (each v\geq v). The base case handles the empty multiset. \square

Editorial

How many ways can 20 twelve-sided dice be rolled so that the top 10 sum to 70? Approach: Enumerate sorted dice configurations (frequency vectors). For each face value from 12 down to 1, decide how many dice show that value. The top-10 sum is determined by the sorted order. For a non-increasing sequence d_1 >= d_2 >= … >= d_20, the top 10 are d_1,…,d_10. Their sum must equal 70. We recurse on face value from 12 down to 1, tracking: At each face value, the count of dice with that value determines how many go to the “top” part (they fill from the top first in descending order). We enumerate frequency vectors for top dice above threshold.

Pseudocode

Enumerate frequency vectors for top dice above threshold
Enumerate bottom dice: (N-H) dice with values in {1,...,t}

Complexity Analysis

  • Time: O(D2NS)O(D^2 \cdot N \cdot S) for the DP-based enumeration of frequency vectors. In practice, the number of valid frequency vectors is bounded and the computation is tractable.
  • Space: O(NS)O(N \cdot S) for the DP tables used in computing multiset counts.

Answer

7448717393364181966\boxed{7448717393364181966}

Code

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

C++ project_euler/problem_240/solution.cpp
#include <bits/stdc++.h>
using namespace std;

/*
 * Problem 240: Top Dice
 *
 * How many ways can 20 twelve-sided dice be rolled so that the top 10 sum to 70?
 *
 * Approach: Enumerate sorted configurations (frequency vectors) by recursing
 * on face values from 12 down to 1, tracking top-10 sum.
 * For each valid configuration, add the multinomial coefficient 20!/prod(n_i!).
 */

typedef long long ll;
typedef unsigned long long ull;

const int NUM_DICE = 20;
const int NUM_SIDES = 12;
const int TOP_COUNT = 10;
const int TARGET = 70;

ll fact[NUM_DICE + 1];
ull total_answer;

void assign_bottom(int face, int dice_left, ll mult_denom) {
    if (dice_left == 0) {
        total_answer += fact[NUM_DICE] / mult_denom;
        return;
    }
    if (face == 0) return;
    if (face == 1) {
        total_answer += fact[NUM_DICE] / (mult_denom * fact[dice_left]);
        return;
    }
    for (int count = 0; count <= dice_left; count++) {
        assign_bottom(face - 1, dice_left - count, mult_denom * fact[count]);
    }
}

void recurse(int face, int dice_left, int top_left, int sum_left, ll mult_denom) {
    if (top_left == 0 && sum_left != 0) return;
    if (top_left == 0 && sum_left == 0) {
        assign_bottom(face, dice_left, mult_denom);
        return;
    }
    if (face == 0) {
        if (dice_left == 0 && top_left == 0 && sum_left == 0)
            total_answer += fact[NUM_DICE] / mult_denom;
        return;
    }
    if (dice_left < top_left) return;
    if ((ll)top_left * face < sum_left) return;
    if (sum_left < top_left) return;

    for (int count = 0; count <= dice_left; count++) {
        int top_from = min(count, top_left);
        int new_top = top_left - top_from;
        int new_sum = sum_left - top_from * face;
        if (new_sum < 0) break;

        recurse(face - 1, dice_left - count, new_top, new_sum,
                mult_denom * fact[count]);
    }
}

int main(){
    fact[0] = 1;
    for (int i = 1; i <= NUM_DICE; i++)
        fact[i] = fact[i-1] * i;

    total_answer = 0;
    recurse(NUM_SIDES, NUM_DICE, TOP_COUNT, TARGET, 1);

    cout << total_answer << endl;
    return 0;
}