Fatoração
Aprendemos na escola que todo número é composto por fatores primos, existindo uma única fatoração pra cada número.
Uma primeira abordagem possível seria passar por todos os números e ir dividindo sempre que possível.
// retorna vetor de pair<primo, expoente> da fatoração
// fatora(36) = [{2, 2}, {3, 2}] ou seja, 36 = 2^2 + 3^2
vector<pair<long long, int>> fatora(long long n) {
vector<pair<long long, int>> ans;
for(long long p = 2; p <= n; p++) {
if(n % p == 0){
int expoente = 0;
while(n % p == 0) {
n /= p;
expoente++;
}
ans.emplace_back(p, expoente);
}
}
return ans;
}
A primeira vista,como queremos decompor em fatores primos, parece que temos que testar se p
é primo. Entretanto passamos por p
de forma crescente e sempre que podemos dividimos n
por p
então a condição (n % p == 0)
só será verdade para p
primos.
Isso ocorre porque todos os fatores primos de um número são menores ou iguais a ele próprio, então quando chegamos em um número, é impossível que ele divida o número e não seja primo, pois se não for, os números primos que o compoem deveriam ter sido contabilizados numa iteração anterior.
Apesar do código acima rodar bem para vários exemplos, no pior caso n
é primo e o código é executado em O(n)
.
Podemos melhorar a complexidade com uma simples observação. É possivel ter apenas um primo maior que a sqrt(n)
, por exemplo, 10 tem 5 como fator e 5 > sqrt(10)
, mas é impossível ter dois primos maiores que a raiz. Se tivermos a > sqrt(n)
e b > sqrt(n)
, quando multiplicamos temos que a * b > sqrt(n) * sqrt(n)
e a * b > n
.
vector<pair<long long, int>> fatora(long long n) {
vector<pair<long long, int>> ans;
for(long long p = 2; p*p <= n; p++) { // comparação que evita o uso de doubles, p <= sqrt(n) é o mesmo que p*p <= n
if(n % p == 0) {
int expoente = 0;
while(n % p == 0) {
n /= p;
expoente++;
}
ans.emplace_back(p, expoente);
}
}
if(n > 1) ans.emplace_back(n, 1);
return ans;
}
Fatoração em O(lg n) para números até N
É possível fatorar números ate um limite N
em O(lg n)
após preprocessamento O(n log( log n))
. O que fazemos é uma pequena modificação no código do crivo, para que enquanto fazemos o crivo, preenchamos um vetor auxiliar lp
, aonde lp[x]
representa o maior número primo que divide x
.
vector<int> lp(N, -1);
for(int x = 2; x < N; x++)
if(lp[x] == -1) { // se x nao foi marcado antes, é primo
for(int m = x; m < N; m += x) // todos os multiplos de i
lp[m] = x;
}
Tendo este vetor podemos fatorar um numero x
com o seguinte procedimento.
vector<pair<int, int>> fatora(int x) {
map<int, int> expoentes;
while(x > 1) {
expoentes[ lp[x] ]++; // aumentamos o expoente do primo lp[x] em 1 na resposta
x /= lp[x];
}
vector<pair<int, int>> ans;
for(pair<int, int> p : expoentes)
ans.emplace_back(p);
return ans;
}
A complexidade do procedimento acima é O(quantidade de fatores)
, que é limitado por O(lg n)
, da para ver que no pior caso todos os fatores são 2(menor primo) e a complexidade é o k
de 2^k = n
.
Número de divisores dada uma fatoração
Vimos que todo inteiro N pode ser escrito de forma única como multiplicação de números primos. Assim,
$$N = {p_1}^{e_1}.{p_2}^{e_2}.{p_3}^{e_3}.{p_4}^{e_4}.{p_5}^{e_5}.{p_6}^{e_6} ... $$onde pi é primo e 0 ei > 0.
Todo divisor de N só pode ter primos que aparecem na fatoração de N e expoente no máximo o do expoente no N. Por exemplo: $$36 = 2^2.3^2$$
$$1 = 2^0.3^0$$ $$2 = 2^1.3^0$$ $$3 = 2^0.3^1$$ $$4 = 2^2.3^0$$ $$6 = 2^1.3^1$$ $$9 = 2^0.3^2$$ $$12 = 2^2.3^1$$ $$18 = 2^1.3^2$$ $$36 = 2^2.3^2$$Para construir um divisor podemos escolher dentre (ei+1) possibilidades para o primo pi. O número total de divisores é a multiplicação desses termos. Assim 36 tem (2+1)*(2+1) = 9 divisores.
Embora esta ideia não melhore a complexidade para encontrar o número de divisores em comparação com as ideias anteriores, ela pode ser uma ferramenta útil para analisar problemas. Por exemplo, os números com exatamente 9 divisores são da forma
$${p_1}^8={p_1}^2.{p_2}^2$$Como por exemplo:
$$256 = 2^8$$ $$6561 = 3^8$$ $$36 = 2^2.3^2$$ $$100 = 2^2.5^2$$ $$255 = 3^2.5^2$$etc…