Factorial mod p — Building fact[] / invfact[] and Legendre's Formula

Watch the factorial table fill forward, the single inverse appear, the inverse-factorial table fill backward, then count the factors of p in n! via Legendre's formula.

step 0 / 0

Phase 1 & 2 — fact[i] = i! mod p (forward), invfact[i] = (i!)-1 mod p (backward)

fact[i] filling (forward) the single inverse (Fermat) invfact[i] filling (backward)

Phase 3 — Legendre: vp(n!) = Σ ⌊n / pi

This is the exponent of p in n!. If it is ≥ 1 (i.e. n ≥ p), then n! ≡ 0 (mod p).

Facts

Big-O

OperationTimeSpaceNote
Build fact[0..N]O(N)O(N)one forward loop
Build invfact[0..N]O(N + log p)O(N)one inverse + backward loop
C(n, k) after precomputeO(1)O(1)three multiplies
Single inverse (Fermat)O(log p)O(1)ap-2 mod p
Legendre vp(n!)O(logp n)O(1)sum of floor divisions

Operation Log

Press Step to fill the factorial table one cell at a time, or Run to animate the whole precompute and Legendre count.

The three phases

The third row of the table, fact·invfact, shows the self-check: it must be 1 in every column (this verifies the inverse table).

Method selector (which algorithm for C(n,k) mod m)

SituationMethodPer query
m prime, n < mfact[]/invfact[] tablesO(1)
m prime, n ≥ mLucas' theoremO(logm n)
m = pe (prime power)Granville (gen. Wilson)O(e·logp n)
m compositeper prime power + CRTΣ + O(r)

This visualization animates the first row (the common large-prime case) plus Legendre's formula, which is the gate that decides when you must leave it for Lucas or Granville.

The core routine (pseudocode)

precompute(N, p): fact[0] = 1 for i in 1..N: fact[i] = fact[i-1] * i % p # Phase 1: forward invfact[N] = pow(fact[N], p-2, p) # Phase 2a: ONE inverse for i in N..1: invfact[i-1] = invfact[i] * i % p # Phase 2b: backward binom(n, k): # O(1) after precompute if k < 0 or k > n: return 0 return fact[n] * invfact[k] % p * invfact[n-k] % p legendre(n, p): # Phase 3: exponent of p in n! e = 0 while n > 0: n //= p; e += n return e

What to try

Worked Legendre examples

nptermsvp(n!)meaning
1025 + 2 + 1828 divides 10!
100520 + 424100! ends in 24 zeros
100333 + 11 + 3 + 148348 divides 100!
67(none, p > n)06! is coprime to 7

Set Legendre n and p above to reproduce any of these in Phase 3. Whenever the total is 0, n! is invertible mod p (the table method applies); whenever it is ≥ 1, n! ≡ 0 (mod p).

Kummer's carry view (why some C(n,k) vanish)

The exponent of p in C(n,k) equals the number of carries when you add k and (n−k) in base p. Example with p = 2:

C(5, 2) mod 2: k = 2 = 10b, n-k = 3 = 11b 10 + 11 ---- 101 # one carry => v_2(C(5,2)) = 1 => C(5,2) = 10 is even C(7, 1) mod 2: k = 1 = 001b, n-k = 6 = 110b 001 +110 ---- 111 # no carry => v_2(C(7,1)) = 0 => C(7,1) = 7 is odd

Why the supporting theorems matter

Self-contained visualization — no external dependencies. The forward loop computes fact[i] = fact[i-1]·i; one Fermat inverse gives invfact[N]; the backward loop computes invfact[i-1] = invfact[i]·i (because (i-1)! = i!/i). Legendre's formula counts the factors of p in n!. See junior.md and professional.md for proofs.