Lançado · em melhoria
Guia de Matemática e teoria dos números · 4/6
Por enquanto, este capítulo está disponível apenas em inglês.
Number-theory complexity is often stated in terms of the value of the input, not the number of inputs. If an algorithm that takes a single number n runs in O(n), it is exponential in the input length, which is the digit count log n. Keep that distinction in mind. This chapter summarizes time and space costs and compares the alternatives.
Every two remainder steps at least halve the larger value, so the loop runs O(log min(a, b)) times; the extended version does a constant factor more work per step. The slowest inputs are consecutive Fibonacci numbers (Lamé's theorem): the quotient is 1 every time, so each step shrinks the numbers as little as possible.
def gcd_steps(a: int, b: int) -> int:
steps = 0
while b:
a, b = b, a % b
steps += 1
return steps
fib = [1, 1]
while len(fib) < 50:
fib.append(fib[-1] + fib[-2])
print(gcd_steps(fib[49], fib[48])) # 48 — numbers around 1.3 × 10^10, only 48 steps
print(gcd_steps(10**18, 3)) # 2No pair of 64-bit values needs more than about 100 steps, so in practice you can treat GCD as constant time.
The loop runs once per bit of the exponent e, with at most two multiplications and reductions per iteration. Multiplying e times is O(e), which never finishes for e = 10^18; fast exponentiation needs about 60 iterations. Space is O(1). Applying the same idea to matrices computes linear recurrences such as Fibonacci numbers in O(k^3 log n).
Each prime p crosses out about n / p slots, so the total work is n × (1/2 + 1/3 + 1/5 + …), and the sum of prime reciprocals grows like log log n. Even for n = 10^7, log log n is about 3, so the sieve is nearly linear. Space is O(n), down to n / 8 bytes with a bit array.
The linear sieve (Euler's sieve) crosses out each composite exactly once for O(n), and records the smallest prime factor (SPF) of every number as a bonus. With an SPF table, any number up to n factors in O(log n).
def linear_sieve(n: int) -> tuple[list[int], list[int]]:
spf = [0] * (n + 1) # spf[k] = smallest prime factor of k
primes: list[int] = []
for i in range(2, n + 1):
if spf[i] == 0:
spf[i] = i
primes.append(i)
for p in primes:
if p > spf[i] or i * p > n:
break
spf[i * p] = p
return primes, spf
def factorize(k: int, spf: list[int]) -> dict[int, int]:
factors: dict[int, int] = {}
while k > 1:
p = spf[k]
factors[p] = factors.get(p, 0) + 1
k //= p
return factors
primes, spf = linear_sieve(1000)
print(len(primes)) # 168
print(factorize(360, spf)) # {2: 3, 3: 2, 5: 1}To factor a single number you do not need a sieve; just try divisors up to √n. For n = 10^12 that is at most 10^6 divisions. If you must factor many numbers as large as 10^18, the usual approach combines the Miller-Rabin primality test with Pollard's rho.
import math
def trial_division(n: int) -> list[int]:
factors = []
d = 2
while d * d <= n:
while n % d == 0:
factors.append(d)
n //= d
d += 1 if d == 2 else 2 # after 2, odd candidates only
if n > 1:
factors.append(n)
return factors
print(trial_division(360)) # [2, 2, 2, 3, 3, 5]
print(trial_division(600851475143)) # [71, 839, 1471, 6857]
print(math.isqrt(10**12)) # 1000000 — the loop boundPrecompute n! and its inverse up to n, and each query becomes two multiplications. Compute only inv_fact[n] with Fermat (O(log p)) and fill the rest backwards with inv_fact[k - 1] = inv_fact[k] × k. For a single query with small r, the direct O(r + log p) method from the previous chapter is simpler. When n ≥ p, the factorials become 0 and you need Lucas's theorem instead.
| Task | Method | Time | Space |
|---|---|---|---|
| GCD | Euclidean algorithm | O(log min(a, b)) | O(1) |
| GCD | Try every candidate divisor | O(min(a, b)) | O(1) |
| Modular inverse | Extended Euclid | O(log m) | O(1) |
| Modular inverse | Fermat (prime modulus) | O(log p) | O(1) |
| Power | Fast exponentiation | O(log e) | O(1) |
| Power | Repeated multiplication | O(e) | O(1) |
| All primes up to n | Sieve of Eratosthenes | O(n log log n) | O(n) |
| All primes up to n | Linear sieve | O(n) | O(n) |
| All primes up to n | Trial division per number | O(n √n) | O(1) extra |
| Factor one number | Trial division | O(√n) | O(1) |
| Factor many numbers | SPF table | O(n) setup, O(log n) per query | O(n) |
| Many nCr mod p | Factorial tables | O(n) setup, O(1) per query | O(n) |
The Euclidean algorithm and fast exponentiation are logarithmic and essentially free. When you need many primes in a range, use a sieve (O(n log log n)); for a single number, trial division (O(√n)) is enough. If you compute many binomial coefficients under the same modulus, precomputing factorials and inverse factorials is the standard move. When you talk about complexity here, always be clear whether you mean the size of the value or the number of its digits.
0 comentários
Fazer login · Faça login para deixar um comentário.
Seja o primeiro a comentar.