Prosti brojevi i faktorizacija

Prirodan broj veći od 1 je prost ako je deljiv samo sa 1 i sa samim sobom. Broj 1 nije prost. Na ovoj strani obrađujemo tri srodna problema koja se često javljaju kao deo složenijih takmičarskih zadataka:

  • provera da li je broj prost i ispis svih prostih brojeva na intervalu,
  • određivanje svih delilaca broja,
  • faktorizacija, odnosno rastavljanje broja na proste činioce.
Problem: odrediti sve proste brojeve na intervalu od a do b.

Prvo rešenje: traženje prvog delioca

Posle učitavanja brojeva a i b prolazimo kroz sve brojeve sa intervala i za svaki proveravamo da li je prost. Funkcija za proveru počinje od delioca 2 i povećava ga sve dok ne naiđe na broj sa kojim je ispitivani broj deljiv. Ako je taj prvi delilac manji od samog broja, broj nije prost; ako je jednak broju, broj je prost.

#include <iostream>
using namespace std;

// Prva (najsporija) verzija: traži prvi delilac broja b počev od 2
bool isProst(long long b)
{
    if (b < 2)          // 0 i 1 nisu prosti brojevi
        return false;

    long long d = 2;
    // Povećavamo d sve dok ne naiđemo na broj sa kojim je b deljiv.
    // Petlja se sigurno završava, jer je b deljiv bar samim sobom.
    while (b % d != 0)
    {
        d++;
    }

    // Ako je prvi pronađeni delilac manji od b, broj nije prost
    return d == b;
}

int main()
{
    long long a, b, c = 0;   // c - broj ispisanih prostih brojeva
    cin >> a >> b;

    for (long long i = a; i <= b; i++)
    {
        if (isProst(i))
        {
            cout << i << " ";
            c++;
            if (c % 10 == 0)     // posle svakih 10 brojeva novi red
                cout << endl;
        }
    }

    return 0;
}

Za prost broj n petlja se izvršava skoro n puta, pa je ovaj algoritam prespor već za brojeve reda 10^9. Ranije se završava samo kada broj ima mali delilac.


Svođenje broja provera sa n na √n

Ako je d delilac broja n, onda je i n/d njegov delilac. Na primer, 6 je delilac broja 42, ali to je i 42/6 = 7. Bar jedan od ta dva delioca mora biti manji ili jednak √n: kada bi oba bila veća, njihov proizvod bi bio veći od n. Ako su jednaki, n je potpun kvadrat i oba su jednaka √n.

Zaključak: ako broj nema delilac između 2 i √n, onda je prost. Dovoljno je najviše √n provera.

#include <iostream>
using namespace std;

// Proverava delioce samo do korena iz n
bool isProst(long long n)
{
    if (n < 2)
        return false;

    long long d = 2;
    while (d * d <= n)       // ekvivalentno sa d <= √n, bez rada sa realnim brojevima
    {
        if (n % d == 0)      // pronađen delilac: n nije prost
            return false;
        d = d + 1;
    }
    return true;             // nema delioca do √n: n je prost
}

int main()
{
    long long a, b, c = 0;
    cin >> a >> b;

    for (long long i = a; i <= b; i++)
    {
        if (isProst(i))
        {
            cout << i << " ";
            c++;
            if (c % 10 == 0)
                cout << endl;
        }
    }

    return 0;
}

Funkcija najpre odbacuje brojeve manje od 2. Zatim ispituje delioce od 2 do √n, uključujući i √n. Uslov se piše kao d * d <= n, da bismo izbegli računanje korena i greške zaokruživanja. Čim se pronađe delilac, funkcija vraća false.


Ubrzanje pomoću oblika 6k ± 1

Svaki prost broj veći od 3 ima oblik 6k − 1 ili 6k + 1. Ostali oblici (6k, 6k ± 2, 6k + 3) deljivi su sa 2 ili sa 3. Zato je dovoljno da posebno proverimo deljivost sa 2 i 3, a zatim kao delioce ispitujemo samo brojeve 5, 7, 11, 13, 17, 19, ... To je oko tri puta manje provera.

#include <iostream>
using namespace std;

// Provera samo delilaca oblika 6k - 1 i 6k + 1
bool isProst(long long n)
{
    if (n < 2)
        return false;
    if (n == 2 || n == 3)
        return true;
    if (n % 2 == 0 || n % 3 == 0)   // parni brojevi i umnošci broja 3
        return false;

    // Preostali mogući delioci su 5, 7, 11, 13, 17, 19, ...
    // odnosno brojevi oblika 6k - 1 i 6k + 1, k = 1, 2, 3, ...
    for (long long d = 5; d * d <= n; d += 6)
    {
        if (n % d == 0 || n % (d + 2) == 0)   // d = 6k - 1, d + 2 = 6k + 1
            return false;
    }
    return true;
}

int main()
{
    long long a, b, c = 0;
    cin >> a >> b;

    for (long long i = a; i <= b; i++)
    {
        if (isProst(i))
        {
            cout << i << " ";
            c++;
            if (c % 10 == 0)
                cout << endl;
        }
    }

    return 0;
}
Česta greška

Ako se izostavi provera n % 2 == 0 || n % 3 == 0, funkcija proverava samo delioce 5, 7, 11, ..., pa brojeve kao što su 4, 6, 8, 9 ili 25 proglašava prostim. Delioci 2 i 3 nisu oblika 6k ± 1 i moraju se ispitati posebno.


Određivanje svih delilaca broja

Određivanje delilaca treba razlikovati od faktorizacije. Delioci broja 12 su 1, 2, 3, 4, 6, 12, svaki naveden po jednom. Faktorizacija je zapis broja kao proizvoda prostih činilaca: 12 = 2 · 2 · 3.

I ovde je dovoljno ispitati brojeve do √n, jer svaki delilac d ima svoj par n/d. Delioci broja 12 su parovi (1, 12), (2, 6), (3, 4), a delioci broja 16 su (1, 16), (2, 8), (4, 4). Kod broja 16 vidimo da je 4 u paru sa samim sobom, jer je 4 = √16.

Algoritmi određivanje svih delilaca broja N
Slika 1: Parovi delilaca brojeva 12 i 16
#include <iostream>
using namespace std;

const int DIM = 10000;
long long d[DIM];   // d[1], d[2], ..., d[m] - pronađeni delioci

// Pronalazi sve delioce broja n; vraća njihov broj m
long long sviDelioci(long long n)
{
    long long m = 0;
    long long i = 1;

    // Delioci dolaze u parovima (i, n / i), pa je dovoljno
    // ispitati brojeve i za koje je i * i < n.
    while (i * i < n)
    {
        if (n % i == 0)
        {
            d[m + 1] = i;        // manji delilac iz para
            d[m + 2] = n / i;    // veći delilac iz para
            m = m + 2;
        }
        i = i + 1;
    }

    // Ako je n potpun kvadrat, koren se dodaje samo jednom
    if (i * i == n)
    {
        m = m + 1;
        d[m] = i;
    }

    return m;
}

int main()
{
    long long N, m;
    cin >> N;

    m = sviDelioci(N);

    for (int i = 1; i <= m; i++)
    {
        cout << d[i] << endl;
    }

    return 0;
}

Delioci se ispisuju u parovima (za 12: 1, 12, 2, 6, 3, 4). Ako su potrebni u rastućem redosledu, niz treba sortirati. Pošto se ispituje samo do √n, program radi brzo i za brojeve reda 10^12.


Faktorizacija broja N

Faktorizacija broja je njegovo rastavljanje na proste činioce, odnosno zapis u obliku:

n = p1^a1 · p2^a2 · ... · pk^ak

Kod određivanja delilaca, posle pronađenog delioca prelazi se odmah na sledeći. Kod faktorizacije se deljenje istim deliocem ponavlja dokle god je broj deljiv njime. Zato su svi pronađeni delioci automatski prosti: kada stignemo do, na primer, delioca 4, sve dvojke su već izdeljene, pa broj više nije deljiv sa 4.

#include <iostream>
using namespace std;

const int DIM = 100;

int a[DIM];   // a[k] - izložilac k-tog prostog činioca
int p[DIM];   // p[k] - k-ti prost činilac

// Rastavlja n na proste činioce; vraća broj različitih činilaca k
int faktorizacija(int n)
{
    int k = 0;
    int d = 2;

    // Uslov d <= n / d je isto što i d * d <= n,
    // ali ne može da izazove prekoračenje opsega tipa int.
    while (d <= n / d)
    {
        if (n % d == 0)
        {
            k = k + 1;
            p[k] = d;
            a[k] = 0;   // izložilac koji se pamti u nizu a

            while (n % d == 0)   // delimo sa d dok god je moguće
            {
                n = n / d;
                a[k] = a[k] + 1;
            }
        }
        d = d + 1;
    }

    // Ako je posle deljenja ostalo n > 1, to je prost činilac
    // veći od korena iz početnog broja (javlja se samo jednom).
    if (n > 1)
    {
        k = k + 1;
        p[k] = n;
        a[k] = 1;
    }

    return k;
}

int main()
{
    int N, k;
    cin >> N;

    k = faktorizacija(N);

    // Ispis svakog prostog činioca onoliko puta koliko se pojavljuje
    for (int i = 1; i <= k; i++)
    {
        for (int j = 1; j <= a[i]; j++)
        {
            cout << p[i] << endl;
        }
    }

    return 0;
}
  • Niz p čuva proste činioce, a niz a njihove izložioce.
  • Ako posle petlje ostane n > 1, to je prost činilac veći od korena početnog broja. Na primer, za N = 26 petlja izdeli dvojku, a ostatak 13 je poslednji činilac.
  • Za N = 360 program ispisuje 2, 2, 2, 3, 3, 5, jer je 360 = 2^3 · 3^2 · 5.
Kada koristiti sito? Ovaj postupak je dobar za faktorizaciju jednog broja. Kada treba faktorisati mnogo brojeva, brže je unapred izračunati najmanje proste delioce pomoću Eratostenovog sita.

Zadatak: Kvadratni parovi — primena faktorizacije

Dat je niz a dužine n. Potrebno je odrediti broj parova indeksa (i, j), gde je 1 ≤ i < j ≤ n, za koje važi da je proizvod ai · aj jednak kvadratu nekog prirodnog broja.

Drugim rečima, tražimo koliko parova elemenata iz niza daje proizvod koji je savršeni kvadrat.


Prethodno
|<Fibonačijev niz
Sledeće
Eratostenovo sito>|