Open Source · 7 Languages · 900+ Problems

Project Euler Solutions

Complete solutions in C++, Python & Java — with step-by-step mathematical explanations

All Problems

Problem 638: Weighted Lattice Paths

View on Project Euler

Project Euler Problem 638 Solution

EulerSolve provides an optimized solution for Project Euler Problem 638, Weighted Lattice Paths, with C++, Python, Java, and a step-by-step mathematical explanation.

Problem Summary Problem 638 asks for a sum of weighted monotone lattice-path counts. For nonnegative integers \(a\) and \(b\), the relevant quantity is $C(a,b,q)=\prod_{j=1}^{k}\frac{1-q^{m+j}}{1-q^j},\qquad k=\min(a,b),\quad m=\max(a,b).$ This is the Gaussian \(q\)-binomial coefficient associated with an \(a\times b\) rectangle. The final target is $S=\sum_{r=1}^{7} C(10^r+r,10^r+r,r)\pmod{10^9+7}.$ The challenge is therefore to evaluate seven very large Gaussian coefficients modulo a prime, not to enumerate individual paths. Mathematical Approach The C++, Python, and Java implementations all use the same algebraic strategy: evaluate the Gaussian product directly, treat the singular case \(q=1\) separately, and accumulate the seven required terms modulo \(10^9+7\). Step 1: Express the weighted path count as a finite product The problem uses the standard Gaussian \(q\)-binomial coefficient $C(a,b,q)=\prod_{j=1}^{k}\frac{1-q^{m+j}}{1-q^j},\qquad k=\min(a,b),\quad m=\max(a,b).$ Because \(k+m=a+b\), this is the \(q\)-analogue of an ordinary binomial coefficient. Writing the formula with \(k=\min(a,b)\) is important computationally, because it keeps the product length as small as possible....

Detailed mathematical approach

Problem Summary

Problem 638 asks for a sum of weighted monotone lattice-path counts. For nonnegative integers \(a\) and \(b\), the relevant quantity is

$C(a,b,q)=\prod_{j=1}^{k}\frac{1-q^{m+j}}{1-q^j},\qquad k=\min(a,b),\quad m=\max(a,b).$

This is the Gaussian \(q\)-binomial coefficient associated with an \(a\times b\) rectangle. The final target is

$S=\sum_{r=1}^{7} C(10^r+r,10^r+r,r)\pmod{10^9+7}.$

The challenge is therefore to evaluate seven very large Gaussian coefficients modulo a prime, not to enumerate individual paths.

Mathematical Approach

The C++, Python, and Java implementations all use the same algebraic strategy: evaluate the Gaussian product directly, treat the singular case \(q=1\) separately, and accumulate the seven required terms modulo \(10^9+7\).

Step 1: Express the weighted path count as a finite product

The problem uses the standard Gaussian \(q\)-binomial coefficient

$C(a,b,q)=\prod_{j=1}^{k}\frac{1-q^{m+j}}{1-q^j},\qquad k=\min(a,b),\quad m=\max(a,b).$

Because \(k+m=a+b\), this is the \(q\)-analogue of an ordinary binomial coefficient. Writing the formula with \(k=\min(a,b)\) is important computationally, because it keeps the product length as small as possible.

Step 2: Interpret it as a weighted lattice-path generating function

The same quantity can be viewed combinatorially as

$C(a,b,q)=\sum_{P} q^{A(P)},$

where the sum runs over monotone lattice paths from \((0,0)\) to \((a,b)\), and \(A(P)\) is a standard area or inversion statistic attached to the path. This interpretation explains why the answer is a polynomial in \(q\) with nonnegative coefficients, while the product form gives the fast evaluation method used in the implementations.

Symmetry of the rectangle also explains why exchanging \(a\) and \(b\) does not change the value, so reducing to \(k=\min(a,b)\) loses no information.

Step 3: Handle the special case \(q=1\)

The product has denominator factors \(1-q^j\), so the case \(q=1\) cannot be plugged in directly. Instead we take the limit term by term:

$\lim_{q\to 1}\frac{1-q^{m+j}}{1-q^j}=\frac{m+j}{j}.$

Multiplying these limits gives

$C(a,b,1)=\prod_{j=1}^{k}\frac{m+j}{j}=\binom{a+b}{a}=\binom{a+b}{b}.$

For the required sum, \(q=1\) appears only in the first term, where \(a=b=11\). That is why the implementation can evaluate this branch with a tiny precomputed factorial table.

Step 4: Evaluate the product modulo the prime \(10^9+7\)

For \(q>1\), the implementations split the product into a numerator and a denominator:

$N=\prod_{j=1}^{k}(1-q^{m+j}),\qquad D=\prod_{j=1}^{k}(1-q^j).$

Then

$C(a,b,q)\equiv N\cdot D^{-1}\pmod M,\qquad M=10^9+7.$

Since \(M\) is prime, the inverse is computed with Fermat's little theorem:

$D^{-1}\equiv D^{M-2}\pmod M.$

The loop is organized to avoid repeated exponentiation inside the product. It computes \(q^m\) once, updates \(q^j\) incrementally, and obtains \(q^{m+j}\) from the product \(q^m\cdot q^j\pmod M\).

Step 5: Apply the formula to Problem 638

Define

$n_r=10^r+r\qquad (1\le r\le 7).$

Because the problem uses equal side lengths, each summand is

$C(n_r,n_r,r)=\prod_{j=1}^{n_r}\frac{1-r^{n_r+j}}{1-r^j}\pmod{10^9+7}.$

Therefore the final answer is

$\boxed{S=\sum_{r=1}^{7} C(n_r,n_r,r)\pmod{10^9+7}.}$

The only qualitative difference between the seven terms is that the first one uses the ordinary binomial shortcut, while the other six use the modular product.

Worked Example: \(C(2,2,2)\)

Here \(k=m=2\), so the product becomes

$C(2,2,2)=\frac{(1-2^3)(1-2^4)}{(1-2)(1-2^2)}=\frac{(-7)(-15)}{(-1)(-3)}=35.$

The same value can be read from the polynomial form

$C(2,2,q)=1+q+2q^2+q^3+q^4.$

Substituting \(q=2\) gives \(35\), while substituting \(q=1\) gives \(6\). These are useful sanity checks because they confirm both the weighted version and the ordinary binomial limit.

How the Code Works

The C++, Python, and Java implementations begin with simple boundary cases. Negative dimensions are rejected, and if either side length is zero the answer is \(1\), because there is exactly one monotone path along the border of the rectangle.

Next they branch on \(q\). For \(q=1\), they evaluate the ordinary binomial coefficient modulo \(10^9+7\) using precomputed factorials and inverse factorials. In this problem that table can stay very small, because the only \(q=1\) contribution is \(C(11,11,1)\).

For \(q>1\), the implementation chooses the smaller of \(a\) and \(b\) as the loop length, computes one initial modular power \(q^m\), then walks through \(j=1,\dots,k\). During that loop it multiplies one accumulator by \(1-q^j\) and the other by \(1-q^{m+j}\), reducing modulo \(10^9+7\) after every step.

Once the loop is finished, the numerator is multiplied by the modular inverse of the denominator. The top-level routine then evaluates the seven required values \(C(10^r+r,10^r+r,r)\) for \(r=1,\dots,7\) and adds them modulo \(10^9+7\).

Complexity Analysis

One evaluation of \(C(a,b,q)\) with \(q>1\) takes \(O(\min(a,b)+\log \max(a,b))\) time. The logarithmic term comes from modular exponentiation, and the linear term comes from the product loop. Extra memory is \(O(1)\), apart from the fixed tiny table used for the \(q=1\) branch.

For Problem 638, the dominant work is the sum of the seven loop lengths:

$\sum_{r=1}^{7}(10^r+r)=11{,}111{,}138.$

So the overall running time is effectively linear in about eleven million modular multiplication steps, which is easily practical in all three languages.

Footnotes and References

  1. Problem page: https://projecteuler.net/problem=638
  2. Gaussian binomial coefficient: Wikipedia - Gaussian binomial coefficient
  3. Lattice path: Wikipedia - Lattice path
  4. Modular multiplicative inverse: Wikipedia - Modular multiplicative inverse
  5. Fermat's little theorem: Wikipedia - Fermat's little theorem

Mathematical approach · C++ solution · Python solution · Java solution

Previous: Problem 637 · All Project Euler solutions · Next: Problem 639

Need help with a problem? Ask me! 💡
e
✦ Euler GLM 5.2
Hi! I'm Euler. Ask me anything about Project Euler problems, math concepts, or solution approaches! 🧮