Teoria dos Números1
Teoria dos Números é um ramo da matemática que estuda os números inteiros. Dominar o maior número possível de tópicos da teoria dos números é importante, pois alguns problemas matemáticos se tornam fáceis (ou mais fáceis) se você conhecer a teoria por trás do problema.
Divisibilidade
Um inteiro \(n\) é divisível por um inteiro \(d\) (denotado por \(d | n\).) se houver outro inteiro \(q\) tal que \(n = d \times q\). Também é dito que \(d\) é um divisor de \(n\). Dividindo os dois lados da igualdade \(n = dq\) por \(d\) tem-se uma definição quase equivalente, ou seja, que \(\frac{n}{d}\) é um inteiro.
Exemplo
O número 12 possui 6 divisores: \(1~(1 \times 12 = 12), 2~(2 \times 6 = 12), 3~(3 \times 4 = 12), 4~(4 \times 3 = 12), 6~(6 \times 6 = 12)\) e \(12~(12 \times 1 = 12)\).
O conceito de divisibilidade traz muitas questões. A primeira é como verificar se um número é divisível por outro. Para número pequenos, que podem ser armazenados em variáveis, por exemplo, do tipo long long
, pode-se usar o operador módulo ou resto da divisão (%
): \(n\) é divisível por \(d\) se e somente se ǹ % d == 0
. Entretanto, para números inteiros grandes a solução não é tão simples. Na Seção Aritmética Modular será discutido como implementar o operador módulo para número inteiros grandes.
Outra questão é como calcular os divisores de um número. Todo inteiro \(n\) tem pelo menos dois divisores (\(1\) e \(n\)). Para encontrar os outros divisores, pode-se usar o fato que qualquer divisor \(d\) de \(n\) deve satisfazer \(|d| \leq |n|\). Assim, pode-se testar se os inteiros entre \(1\) e \(n\) são divisores de \(n\), ou seja, um algoritmo \(O(n)\). Entretanto, sempre que tem-se um divisor \(d\), tem-se outro divisor \(q\) (veja o exemplo anterior). Por exemplo, ao afirmar que \(3\) é um divisor de \(12\), pois \(3 \times 4 = 12\), tem-se outro divisor, \(4\). Ou seja, os divisores vêm em pares. Veja outros exemplo:
Exemplo
O número 16 possui 5 divisores: \(1~(1 \times 16 = 16), 2~(2 \times 8 = 16), 4~(4 \times 4 = 16), 8~(8 \times 2 = 16)\) e \(16~(16 \times 6 = 16)\).
Dessa forma, pode-se limitar a encontrar cada elemento desses pares. Além disso, um dos valores de cada par deve ser limitado por \(\sqrt n\). Por quê? Esse limite ajuda a reduzir a complexidade do algoritmo que encontra todos os divisores de um número em \(O(\sqrt n)\). A função abaixo retorna um vector
com todos os divisores de \(n\).
x <= sqrt(n)
é o mesmo quex*x <= n
.- Em alguns casos, é interessante ou necessário retornar os divisores ordenados
Complemente sua leitura e seu conhecimento:
- Number of divisors / sum of divisors
- Divisibility
- Counting Divisors of a Number in \(O(n^{\frac{1}{3}})\)
- How many divisors does a number have?
Números Primos
Um número inteiro \(n > 1\) é chamado de número primo se e somente se possui dois divisores: \(1\) e \(n\). Um número que não é primo é chamado de número composto (veja a figura abaixo). O primeiro e único número primo par é \(2\). Os próximos números primos são: \(3, 5, 7, 11, 13, \dots\). Como você deve imaginar, existe um número infinito de primos (Veja a prova aqui).
Número primo é um tópico importante da teoria dos números e a fonte de muitos problemas em programações competitivas. Por isso é de extrema importância conhecer e dominar alguns algoritmos que envolvam números primos.
Testes de Primalidade
Se um número \(n\) não é primo, então ele pode ser representado pelo produto de dois inteiros \(a \times b\), onde \(a \leq \sqrt n\) ou \(b \leq \sqrt n\). Com isso, pode-se testar se um número é primo ou não e encontrar uma decomposição (fatoração) em fatores primos em \(O(\sqrt n)\). A função abaixo verifica se um dado número \(n\) é primo ou não.
x <= sqrt(n)
é o mesmo quex*x <= n
.
Ou, alternativamente:
- Fonte: primes.cpp
Complemente sua leitura e seu conhecimento:
Decomposição em fatores primos
Todo número positivo \(n\) possui uma decomposição (fatoração) em fatores primos única: uma forma de decompor \(n\) em um produto de números primos, ou seja:
onde \(p_i\) são números primos distintos e \(a_i\) inteiros positivos.
A função abaixo retorna um vector
com a decomposição em fatores primos de \(n\).
Note que cada fator primo aparece no vetor o número de vezes que ele divide \(n\). Por exemplo, \(24 = 2^3 \times 3\), então o resultado da função é \([2,2,2,3]\).
Complemente sua leitura e seu conhecimento:
Crivo de Eratóstenes
O Crivo de Eratóstenes é um algoritmo para encontrar todos os números primos até um certo limite usando \(O(n \log \log n)\) operações.
A ideia do algoritmo é a seguinte: inicialmente, escreve-se todos os números entre \(2\) e \(n\). Então, marca-se todos os múltiplos de \(2\) (já que \(2\) é o menor número primo). Em seguida, pega-se o próximo valor que não foi marcado como composto, neste caso, é o \(3\). Isso significa que 3 é primo. Então, marca-se todos os múltiplos de 3 como compostos. O próximo número não marcado é o \(5\) (próximo número primo). Marca-se todos os múltiplos de \(5\). Este processo é repetido até \(n\). A animação abaixo exemplifica a execução do algoritmo para \(n = 120\).
O código abaixo exemplifica uma possível implementação do algoritmo. Esse algoritmo possui complexidade \(O(n \log \log n)\) (veja a prova aqui).
- Cria um array (vector) booleano de tamanho \(n + 1\), onde todas as posições são inicializadas com
true
. - Iteramos por todos os números divisíveis pelo primo
i
Complemente sua leitura e seu conhecimento:
Primo de Mersenne
Número de Mersenne é todo número natural da forma \(M_{n}=2^{n}-1\), onde \(n\) é um número natural. Os primeiros números Mersenne são: \(0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, \dots\). Um subconjunto particularmente interessante é o constituído pelos números de Mersenne que são também primos: os primos de Mersenne. Note que nem todo número de Mersenne é primo, assim como nem todo número primo é de Mersenne.
Os primeiros primos de Mersenne são: \(M_2 = 3, M_3 = 7, M_5 = 31, M_7 = 127, M_13 = 8191, M_17 = 131071, M_19 = 524287, \dots\)
Um resultado elementar sobre os números de Mersenne afirma que se \(2^{n}-1\) é um número primo, então \(n\) também é um número primo.
Algoritmo de Euclides (MDC/MMC)
O máximo divisor comum (GCD, do inglês greatest common divisor) dos números \(a\) e \(b\), gcd(a,b)
, é o maior número que divide \(a\) e \(b\), e o mínimo múltiplo comum (LCM, do inglês least common multiple) de \(a\) e \(b\), lcm(a,b)
, é o menor número que é divisível por \(a\) e \(b\).
O algoritmo de Euclides fornece uma maneira eficiente de encontrar o máximo divisor comum de dois números. O algoritmo é baseado na seguinte definição:
Usando essa definição, o algoritmo é facilmente implementado usando recursão:
Dica
Você pode usar a função integrada __gcd(a, b)
do C++.
Pode-se mostrar que o algoritmo de Euclides possui complexidade \(O(\log n)\), onde \(n = min(a,b)\).
O mínimo múltiplo comum (LCM) pode ser calculado da seguinte forma:
Para calcular o GCD ou LCM de mais de dois valores, pode-se calcular o valor de dois em dois (em qualquer ordem). Por exemplo:
Função totiente de Euler
A função totiente de Euler, também conhecida como função \(\phi (n)\), conta o número de inteiros entre \(1\) e \(n\), no qual são coprimos de \(n\). Dois números são coprimos se o máximo divisor comum entre eles for 1 (1 é considerado ser coprimo para qualquer número). Por exemplo, \(\phi (12) = 4\), pois 1, 5, 7 e 11 são coprimos de 12.
Abaixo estão valores de \(\phi (n)\) para os primeiros números inteiros positivos:
A função abaixo calcula o valor de \(\phi (n)\) em \(O(\sqrt n)\).
Complemente sua leitura e seu conhecimento:
Aritmética Modular
Na aritmética modular, o conjunto de números é limitado de forma que apenas os números \(0,1,2,\dots,m−1\) são usados, onde \(m\) é uma constante. Cada número \(x\) é representado pelo número \(x \bmod m\): o resto da divisão de \(x\) por \(m\). Por exemplo, se \(m = 23\), em vez \(x = 247\), considera-se \(x \bmod 23 = 17\). Normalmente, \(m\) será um primo grande, dado no problema, normalmente, \(10^9 + 7\). A aritmética modular é usada para evitar integer overflow.
As seguintes propriedades valem no cálculo do módulo:
O que significa que se a resposta está sendo computada por meio de adições, subtrações e multiplicações, e no final você precisa tirar o módulo dela, você pode tirar o módulo em todas as operações intermediárias que isso não afetará a resposta.
Exponenciação Binária
A exponenciação binária é um truque que permite calcular \(x^n\) usando apenas multiplicações \(O(\log n)\) (em vez das multiplicações \(O(n)\) exigidas pela abordagem ingênua).
Sabe-se que \(x^a \cdot x^b = x^{a+b}\). Em particular, \(x^{2b} = x^b \cdot x^b\). Logo, se o expoente \(n\) de \(x^n\) for par, pode-se dizer que \(x^n = x^{\frac{n}{2}} \cdot x^{\frac{n}{2}}\). No entanto, se \(n\) for ímpar, tem-se algo similar: \(x^n = x^{\frac{n-1}{2}} \cdot x^{\frac{n-1}{2}} \cdot x\). Dessa forma, pode-se montar a seguinte recorrência:
Note que se \(n\) for par, o valor \(x^{n/2}\) deve ser calculado apenas uma vez. Isso garante que a complexidade do algoritmo seja \(O(\log n)\). A função abaixo calcula do valor de \(x^n\):
Alternativamente, sem usar recursão e usando manipulação de bits:
Em alguns casos, é necessário calcular o valor de \(x^n \bmod m\). Sabendo que \((a \cdot b) \pmod{m} = ((a \bmod m) \cdot (b \bmod m)) \bmod m\), pode-se usar diretamente código anterior e apenas substituir cada multiplicação por uma multiplicação modular:
- Deixando o valor padrão de
m
como 1, pode-se usar essa função sem passar o valor dem
como parâmetro.
Dica
Se \(m\) é um número primo, pode-se acelerar um pouco este algoritmo calculando \(x^{n \bmod(m−1)}\) em vez de \(x^n\).
Complemente sua leitura e seu conhecimento: