Fonction totient d'Euler : théorie et algorithmes de calcul

La fonction indicatrice d'Euler, notée \(\varphi(n)\), compte le nombre d'enteirs posiitfs inférieurs ou égaux à \(n\) qui sont premiers avec \(n\). Si \(p\) est un nombre premier, on a immédiatement \(\varphi(p) = p - 1\).

Propriétés fondamentales

La fonctoin totient possède plusieurs propriétés remarquables :

  • Multiplicativité : si \(\gcd(a, b) = 1\), alors \(\varphi(ab) = \varphi(a)\varphi(b)\).
  • Facteur commun : si \(a \mid b\), alors \(\varphi(ab) = a\varphi(b)\).
  • Formule produit : si \(n = p_1^{c_1} p_2^{c_2} \ldots p_s^{c_s}\) est la décomposition en facteurs premiers de \(n\), alors :

\(\displaystyle \varphi(n) = n \prod_{i=1}^{s} \left(1 - \frac{1}{p_i}\right)\)

Pour une puissance de premier \(p^c\), il y a exactement \(p^{c-1}\) multiples de \(p\) parmi les entiers de \(1\) à \(p^c\), d'où :

\(\displaystyle \varphi(p^c) = p^c - p^{c-1} = p^{c-1}(p - 1)\)

La formule produit découle alors de la multiplicativité de \(\varphi\).

Calcul d'une valeur isolée

Une approche consiste à factoriser \(n\) et à appliquer directement la formule produit. Pour chaque diviseur premier découvert, on met à jour le résultat :

long long totient_isole(long long n) {
    long long res = n;
    for (long long d = 2; d * d <= n; ++d) {
        if (n % d == 0) {
            res -= res / d;
            while (n % d == 0) n /= d;
        }
    }
    if (n > 1) res -= res / n;
    return res;
}

Calcul en masse par crible

Pour obtenir \(\varphi(i)\) pour tous les \(i \le n\), les méthodes suivantes sont plus efficaces.

Crible d'Eratosthène

On initialise \(\varphi[i] = i\). Pour chaque nombre premier \(i\), on parcourt ses multiples et on leur applique le facteur \(\frac{i-1}{i}\), ce qui équivaut à \(\varphi[j] = \varphi[j] - \frac{\varphi[j]}{i}\). La complexité est en \(\mathcal{O}(n \log \log n)\).

void totient_era(int n) {
    for (int i = 1; i <= n; ++i) phi[i] = i;
    for (int i = 2; i <= n; ++i) {
        if (phi[i] == i) {
            for (int j = i; j <= n; j += i) {
                phi[j] -= phi[j] / i;
            }
        }
    }
}

Crible linéaire d'Euler

Ce crible garantit que chaque nombre est traité en temps constant amorti, soit une complexité globale \(\mathcal{O}(n)\). On exploite le fait que tout composé est généré par son plus petit facteur premier. Si \(p\) divise \(i\), alors \(\varphi(ip) = p\varphi(i)\) ; sinon \(\varphi(ip) = (p - 1)\varphi(i)\).

const int LIM = 10000005;
int phi[LIM], prem[LIM];
bool compose[LIM];

void totient_lineaire(int n) {
    phi[1] = 1;
    int cnt = 0;
    for (int i = 2; i <= n; ++i) {
        if (!compose[i]) {
            prem[++cnt] = i;
            phi[i] = i - 1;
        }
        for (int j = 1; j <= cnt && 1LL * i * prem[j] <= n; ++j) {
            compose[i * prem[j]] = 1;
            if (i % prem[j] == 0) {
                phi[i * prem[j]] = phi[i] * prem[j];
                break;
            } else {
                phi[i * prem[j]] = phi[i] * (prem[j] - 1);
            }
        }
    }
}

Étiquettes: euler-totient linear-sieve eratosthenes-sieve number-theory cpp

Publié le 29 juin à 18h21