Note que se então e possuem algum fator primo em comum. Usando o fato de que entre e um número divide exatamente (divisão inteira) números. Então, podemos calcular o conjunto de primos que dividem e depois somamos todas as divisões inteiras para cada primo de , porém, alguns primos podem acabar dividindo o mesmo número, ou seja, no final o nosso código vai contar o mesmo número duas vezes. Por exemplo:
Se , então . divide os números e divide os números . Note que quando na verdade a resposta é . Isso se dá por que o número foi contado duas vezes.
Então como resolver esse problema de que os números podem acabar sendo contados várias vezes? Simples, usando o princípio da inclusão-exclusão que diz, dados dois conjuntos e :
Então, para os dois conjuntos do exemplos, se for o conjunto dos múltiplos de menores ou iguais a e o de então
Portanto, basta calcular todos os primos divisores de usando o algoritmo do crivo de Eratóstenes, já que , e depois usar inclusão-exclusão nos primos que o dividem.
Código para melhor entendimento:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <bits/stdc++.h> | |
using namespace std; | |
const int maxn = 1e6 + 10; | |
bool composto[maxn]; | |
int n; | |
vector<int> p[maxn]; | |
void sieve() | |
{ | |
memset(composto, 0, sizeof(composto)); | |
composto[1] = true; // 1 nao eh primo | |
for(int i = 2; i < maxn; i++) | |
{ | |
if(!composto[i]) // se i eh primo | |
{ | |
p[i].push_back(i); // i divide i | |
for(int j = i + i; j < maxn; j+= i) // percorro os multiplos de i | |
{ | |
composto[j] = true; // um multiplo de 1 nao eh primo | |
p[j].push_back(i);// i divide um multiplo de i | |
} | |
} | |
} | |
} | |
int func(int val, int i, int s = 1) // funcao recursiva para calcular a inclusao-exclusao | |
{ | |
// nesse caso "val" eh o valor do produto dos primos, "i" eh o indice do primo no vector p | |
// e "s" eh o sinal da inclusao-exclusao | |
int sum = 0; | |
if(i == p[n].size()) return sum; // se ja visitei todos os primos que dividem N | |
sum += s*(n/(val*p[n][i])) + func(val*p[n][i], i + 1, s*-1); //nesse caso eu uso o i-esimo primo | |
// e calculo quantos numeros (val * p[n][i]) divide fazendo sempre a inclusao exclusao | |
sum += func(val, i + 1, s); // caso eu nao use o i-esimo primo | |
return sum; | |
} | |
int main() | |
{ | |
sieve(); | |
int t; | |
cin >> t; | |
while(t--) | |
{ | |
cin >> n; | |
cout << func(1, 0, 1) << "\n"; | |
} | |
} |