τύφλα να 'χει το e-maxx

Θεωρία αριθμών για διαγωνισμούς πληροφορικής

Η θεωρία αριθμών είναι ο τομέας των μαθηματικών που μελετάει τις ιδιότητες των ακεραίων αριθμών. Αρκετά από τα θεωρήματα που περιέχονται στην θεωρία αριθμών μπορεί να είναι χρήσιμα και στην πληροφορική. Σε αυτή τη σελίδα θα αναφερόμαστε μόνο σε ακέραιους αριθμούς (και κυρίως σε φυσικούς).

Πίνακας συμβόλων

Σύμβολο Εξήγηση
Το υπόλοιπο της διαίρεσης του με το
Το υπόλοιπο της διαίρεσης του με το ισούται με το υπόλοιπο της διαίρεσης του με το
Το ακέραιο μέρος του κλάσματος . Εάν το κλάσμα είναι μεγαλύτερο του μηδενός στρογγυλοποιούμε προς τα κάτω, αλλιώς στρογγυλοποιούμε προς τα πάνω.
Ο διαιρεί τον . Αντίστοιχα το σύμβολο σημαίνει ότι ο δεν διαιρεί τον .
Ο μέγιστος κοινός διαιρέτης των

Υπόλοιπο

Σύμφωνα με την Ευκλείδεια διαίρεση, όταν διαιρούμε τον διαιρετέο με τον διαιρέτη , υπάρχει μοναδικό πηλίκο και υπόλοιπο έτσι ώστε , με . Θα γράφουμε (το είναι ισοϋπόλοιπο του διά του ) και (το ισούται με το υπόλοιπο της διαίρεσης του με το ).

Πράξεις με υπόλοιπο

Σε πολλά προβλήματα μας ζητείται να επιστρέψουμε το αποτέλεσμα μιας πράξης με κάποιο υπόλοιπο, δηλαδή δίνεται κάποιο , και αν το αποτέλεσμα του αλγορίθμου μας ήταν , μας ζητείται να επιστρέψουμε τον αριθμό .

Πρόσθεση και πολλαπλασιασμός

Η πρόσθεση, η αφαίρεση και ο πολλαπλασιασμός γίνονται κανονικά όταν έχουμε τον ίδιο διαιρέτη. Δηλαδή:

.

Εύρεση δύναμης με υπόλοιπο

Αν και αυτός ο αλγόριθμος δεν αφορά μόνο τις περιπτώσεις που θέλουμε τη δύναμη ενός αριθμού με υπόλοιπο κάποιον άλλον αριθμό, σε αυτές τις περιπτώσεις είναι πιο χρήσιμος. Είναι ένας αναδρομικός αλγόριθμος, που για να υπολογίσει τη δύναμη ενός αριθμού στον εκθέτη με υπόλοιπο , χρειάζεται πράξεις.

int powmod(int base, int exp, int mod) {
    if(exp==0) {
        return 1;
    }
    long long half=powmod(base, exp/2, mod);
    if(exp%2==0) {
        return half*half%mod;
    }
    else {
        return half*half*base%mod;
    }
}

Πολλαπλασιαστικό αντίστροφο (διαίρεση)

Δυστυχώς, η διαίρεση δεν είναι το ίδιο απλή με τις υπόλοιπες πράξεις. Θα ορίσουμε το πολλαπλασιαστικό αντίστροφο με υπόλοιπο ενός αριθμού , ως τον (ακέραιο πάντα) αριθμό μικρότερο του (όπου είναι ο διαιρέτης), για τον οποίο ισχύει . Για παράδειγμα, το πολλαπλασιαστικό αντίστροφο του 2, για είναι το 2, αφού . Υπάρχουν περιπτώσεις όμως που δεν υπάρχει πολλαπλασιαστικό αντίστροφο. Για παράδειγμα, για , ο αριθμός 2 δεν έχει πολλαπλασιαστικό αντίστροφο. Ισχύει όμως ότι όταν ο διαιρέτης είναι πρώτος αριθμός, τότε πάντα υπάρχει πολλαπλασιαστικό αντίστροφο. Από εδώ και πέρα θα αναφερόμαστε σε πολλαπλασιαστικό αντίστροφο μόνο για πρώτους αριθμούς.

Πρώτοι αριθμοί

Οι πρώτοι αριθμοί λέγονται οι φυσικοί αριθμοί οι οποίοι διαιρούνται μόνο από τον εαυτό τους και το . Πρώτοι αριθμοί είναι οι

Ισχύει ότι όλοι οι ακέραιοι αριθμοί μπορούν να γραφούν με μοναδικό τρόπο ως γινόμενο δυνάμεων πρώτων αριθμών.

Ύπαρξη πρώτου διαιρέτη μικρότερου της ρίζας

Δηλαδή, εάν ένας αριθμός δεν είναι πρώτος, τότε υπάρχει ένας πρώτος έτσι ώστε και .

Η απόδειξη είναι ιδιαίτερα εύκολη. Έστω ότι όλοι οι πρώτοι διαιρέτες ενός αριθμού είναι καθαρά μεγαλύτεροι της ρίζας του. Αφού ο αριθμός δεν είναι πρώτος, τότε θα υπάρχουν τουλάχιστον δύο (όχι απαραίτητα διαφορετικοί, αλλά τέτοιο ώστε το γινόμενό τους να διαιρεί το ) πρώτοι αριθμοί που τον διαιρούν, έστω . Τότε, , άτοπο αφού αναγκαστικά ισχύει .

Από αυτό το αποτέλεσμα έχουμε έναν γρήγορο και εύκολο τρόπο να ελέγξουμε αν ένας αριθμός είναι πρώτος, διότι αρκεί να ελέγξουμε εάν τον διαιρεί κάποιος αριθμός ο οποίος είναι μικρότερος ή ίσος της ρίζας του. Επομένως, για έναν αριθμό , μπορούμε να ελέγξουμε σε χρόνο εάν είναι πρώτος.

bool is_prime(int n) {
    if(n==0 || n==1) {
        return false;
    }
    for(int i=2; i<=sqrt(n); ++i) {
        if(n%i==0) {
            return false;
        }
    }
    return true;
}

Το μικρό θεώρημα του Fermat

Σύμφωνα με το μικρό θεώρημα του Fermat, εάν πρώτος και ακέραιος, όχι πολλαπλάσιο του , τότε .

Εύρεση πολλαπλασιαστικού αντιστρόφου

Χρησιμοποιώντας αυτό το αποτέλεσμα, μπορούμε εύκολα να βρούμε το πολλαπλασιαστικό αντίστροφο ενός αριθμού n, όταν ο διαιρέτης είναι πρώτος. Να υπενθυμίσουμε ότι στην περίπτωση που ο διαιρέτης είναι πρώτος, υπάρχει πάντα πολλαπλασιαστικό αντίστροφο. Έχουμε ότι . Είναι εμφανές ότι η πολυπλοκότητα του αλγορίθμου είναι .

int invmod(int num, int mod) {
    return powmod(num, mod-2, mod);
}

Ο αλγόριθμος του Ευκλείδη

Ο αλγόριθμος του Ευκλείδη είναι ένας αλγόριθμος εύρεσης του ΜΚΔ (μέγιστου κοινού διαιρέτη) δύο αριθμών. Να σημειωθεί ότι για την εύρεση του μέγιστου κοινού διαιρέτη δύο αριθμών μπορούμε να χρησιμοποιήσουμε την εντολή της c/c++, __gcd(a,b). Μια χρήσιμη ιδιότητα του μέγιστου κοινού διαιρέτη είναι ότι μπορούμε να τον χρησιμοποιήσουμε για να βρούμε το ελάχιστο κοινό πολλαπλάσιο αφού το γινόμενο του μέγιστου κοινού διαιρέτη και του ελάχιστου κοινού πολλαπλασίου ισούται με το γινόμενο των αριθμών.

Ο αλγόριθμος του Ευκλείδη βασίζεται στο γεγονός ότι . Αυτό βασίζεται στο γεγονός ότι . Αν , τότε , επομένως θα χρειαστεί πεπερασμένος αριθμός βημάτων, αφού σε κάθε βήμα μικραίνει ένας από τους δύο αριθμούς. Όταν φτάσουμε στο σημείο όπου ή , γνωρίζουμε απευθείας τον μέγιστο κοινό διαιρέτη. Αποδεικνύεται ότι τα βήματα που χρειάζεται ο αλγόριθμος του Ευκλείδη για να βρει τον μέγιστο κοινό διαιρέτη είναι . Προσοχή να φροντίζουμε να έχουμε τον μικρότερο αριθμό δεύτερο έτσι ώστε πράγματι να μικραίνει ένας από τους δύο αριθμούς σε κάθε βήμα.

int gcd(int a, int b) {
    if(b==0) {
        return a;
    }
    else {
        return gcd(b, a%b);
    }
}
long long lcm(int a, int b) {
    return (long long)a/(long long)gcd(a,b)*(long long)b;
}

Ο λόγος που γράψαμε a/gcd(a,b)*b και όχι a*b/gcd(a,b) είναι για να αποφύγουμε την περίπτωση που η τιμή του υπερβαίνει την μέγιστη τιμή ακεραίου, αλλά η τιμή δεν την υπερβαίνει. Επίσης το θέσαμε ως long long γιατί υπάρχει η περίπτωση οι αριθμοί να είναι πρώτοι μεταξύ τους, οπότε το ελάχιστο κοινό πολλαπλάσιο θα ισούται με το γινόμενό τους και ο αριθμός αυτός θα είναι πολύ μεγάλος για να χωρέσει σε int.

Επέκταση του αλγορίθμου του Ευκλείδη

Ο αλγόριθμος του Ευκλείδη μπορεί να επεκταθεί έτσι ώστε να βρει ακέραιες λύσεις για τα στην εξίσωση .

Είδαμε ήδη ότι . Ας υποθέσουμε ότι έχουμε βρει μια λύση στην εξίσωση , και ψάχνουμε τη λύση στην εξίσωση . Γράφουμε . Οπότε έχουμε ότι και .

struct gcd_tuple {
    int x, y, g;
};
gcd_tuple ext_gcd(int a, int b) {
    gcd_tuple out;
    if(a==0) {
        out.x=0;
        out.y=1;
        out.g=b;
        return out;
    }
    gcd_tuple prev=ext_gcd(b%a, a);
    out.x=prev.y-b/a*prev.x;
    out.y=prev.x;
    out.g=prev.g;
    return out;
}

Λύση Διοφαντικών εξισώσεων

Οι Διοφαντικές εξισώσεις είναι εξισώσεις στους ακεραίους αριθμούς της μορφής , όπου οι άγνωστοι είναι τα . Προφανώς αν τότε υπάρχουν άπειρες λύσεις και αν δεν υπάρχει καμία λύση. Ισχύει ότι οι Διοφαντικές εξισώσεις έχουν λύση αν και μόνο αν . Μπορούμε λοιπόν να λύσουμε μια Διοφαντική εξίσωση με τη χρήση της επέκτασης του αλγορίθμου του Ευκλείδη.

struct diof_tuple {
    int x, y;
    bool has_sol;
};
diof_tuple solve_diof(int a, int b, int c) {
    diof_tuple out;
    gcd_tuple g=ext_gcd(a,b);
    if((g.g==0 && c!=0) || c%g.g!=0) {
        out.has_sol=false;
        return out;
    }
    if(g.g==0) {
        out.has_sol=false;
        return out;
    }
    out.x=g.x*(c/g.g);
    out.y=g.y*(c/g.g);
    if(a<0) {
        out.x*=-1;
    }
    if(b<0) {
        out.y*=-1;
    }
    out.has_sol=true;
    return out;
}

Εάν θέλουμε να βρούμε όλες τις λύσεις μιας Διοφαντικής εξίσωσης μπορούμε να χρησιμοποιήσουμε το γεγονός ότι αν έχουμε μια λύση , τότε για κάθε ακέραιο , ισχύει ότι κάθε ζευγάρι εξακολουθεί να είναι λύση της Διοφαντικής εξίσωσης.

Εύρεση πολλαπλασιαστικού αντιστρόφου

Υπενθυμίζουμε ότι το πολλαπλασιαστικό αντίστροφο ενός αριθμού ως προς διαιρέτη είναι ο φυσικός αριθμός για τον οποίο ισχύει ότι και αυτός ο αριθμός υπάρχει αν και μόνο αν . Ας θεωρήσουμε την Διοφαντική εξίσωση . Ήδη γνωρίζουμε ότι , αφού . Άρα αρκεί να χρησιμοποιήσουμε την επέκταση του αλγορίθμου του Ευκλείδη για να λύσουμε την εξίσωση αυτή, η οποία ήδη ξέρουμε πως έχει τουλάχιστον μία λύση. Τότε, θα ισχύει ότι , επομένως θα έχουμε βρει το πολλαπλασιαστικό αντίστροφο του .

int invmod(int num, int mod) {
    gcd_tuple g=ext_gcd(num, mod);
    if(g.g!=1) {
        return 0;
    }
    return (g.x%mod+mod)%mod;
}

Κόσκινα

Κόσκινο του Ερατοσθένη

Το κόσκινο του Ερατοσθένη βρίσκει όλους τους πρώτους μέχρι κάποιο όριο . Η πολυπλοκότητά του είναι . Εάν γνωρίζουμε όλους τους πρώτους μέχρι κάποιον αριθμό , τότε γνωρίζουμε ότι όλα τους τα πολλαπλάσια δεν είναι πρώτοι. Αν γράψουμε σε έναν πίνακα όλους τους αριθμούς μέχρι το και σβήσουμε τα πολλαπλάσια των γνωστών πρώτων, τότε οι αριθμοί που θα απομείνουν πρέπει αναγκαστικά να περιέχουν πρώτους. Μάλιστα, ο ελάχιστος από τους εναπομείναντες αριθμούς θα πρέπει να είναι πρώτος, διότι δεν τον διαιρεί κανένας άλλος αριθμός εκτός από τον εαυτό του και το 1. Άρα, έχουμε αποκτήσει έτσι έναν καινούριο πρώτο αριθμό, και μπορούμε να συνεχίσουμε τη διαδικασία αυτή μέχρι να βρούμε όλους τους πρώτους μέχρι το . Ας δούμε ένα παράδειγμα, όπου :

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

2 3 5 7 9 11 13 15 17 19

2 3 5 7 11 13 17 19

bool notprime[MAXN];
vector<int> prime_sieve(int n) {
    vector<int> primes;
    for(int i=2; i<=n; ++i) {
        if(notprime[i]) {
            continue;
        }
        primes.push_back(i);
        for(int j=2*i; j<=n; j+=i) {
            notprime[j]=true;
        }
    }
    return primes;
}

Κόσκινο πολλαπλασιαστικών αντιστρόφων

Με αυτό το κόσκινο μπορούμε να βρούμε όλα τα πολλαπλασιαστικά αντίστροφα των αριθμών σε , όπου είναι ο διαιρέτης. Εάν είναι ο (πρώτος) διαιρέτης και είναι ο αριθμός για τον οποίο θέλουμε να υπολογίσουμε το πολλαπλασιαστικό αντίστροφο, έχοντας ήδη υπολογίσει το πολλαπλασιαστικό αντίστροφο για όλους τους αριθμούς μέχρι το . Ισχύει ότι:

Γνωρίζουμε ότι ο αριθμός είναι ακέραιος, και ότι , επομένως το αντίστροφο του αριθμού αυτού το έχουμε ήδη υπολογίσει.

int inv[MAXN];
void inverse_sieve(int mod) {
    inv[1]=1;
    for(int i=2; i<mod; ++i) {
        inv[i]=(mod-mod/i*inv[mod%i]%mod)%mod;
    }
}
int invmod(int num, int mod) {
    return inv[(num%mod+mod)%mod];
}

Να σημειωθεί ότι στην περίπτωση όπου ο δεν είναι πρώτος, ο αλγόριθμος αυτός εξακολουθεί να είναι σωστός (σε αντίθεση με τον αλγόριθμο που βασίζεται στο μικρό θεώρημα του Fermat), αλλά όταν δεν υπάρχει πολλαπλασιαστικό αντίστροφο για κάποιον αριθμό , τότε inv[i]=0.

Γενικά

Επιπλέον για τους πρώτους

Σύμφωνα με το Θεώρημα Πρώτων Αριθμών, το πλήθος των πρώτων μέχρι τον έχει τάξη μεγέθους .

Επίσης ισχύει ότι εάν πρώτος αριθμός και , τότε οι δυνάμεις του με υπόλοιπο το παίρνουν κυκλικά όλες τις θετικές τιμές μικρότερες του . Δηλαδή .

Επιπλέον κόσκινα

Παραγοντοποίηση αριθμού

Ήδη γνωρίζουμε ότι όλοι οι ακέραιοι μπορούν να παραγοντοποιηθούν με μοναδικό τρόπο. Ας εξετάσουμε το πρόβλημα της παραγοντοποίησης αριθμού. Η προφανής λύση είναι να χρησιμοποιήσουμε το κόσκινο του Ερατοσθένη και να το εφαρμόσουμε για να παραγοντοποιήσουμε τους αριθμούς. Αλλά ας δούμε πώς μπορούμε να μετατρέψουμε το κόσκινο του Ερατοσθένη για να λύσουμε το συγκεκριμένο αυτό πρόβλημα. Θα δημιουργήσουμε ένα κόσκινο έτσι ώστε να ξέρουμε τον ελάχιστο πρώτο που διαιρεί τον κάθε ακέραιο. Εάν γνωρίζουμε για κάθε αριθμό τον ελάχιστο πρώτο που τον διαιρεί, τότε μπορούμε να προχωρήσουμε αναδρομικά μέχρι να φτάσουμε στον αριθμό 1.

Για να φτιάξουμε αυτό το κόσκινο, θα κάνουμε την ίδια διαδικασία με το κόσκινο του Ερατοσθένη, αλλά κάθε φορά που σημειώνουμε ότι ένας αριθμός δεν είναι πρώτος, αντί για αυτό θα ενημερώνουμε τον ελάχιστο πρώτο που τον διαιρεί.

int mnprime[MAXN];
void div_sieve(int n) {
    for(int i=1; i<=n; ++i) {
        mnprime[i]=i;
    }
    for(int i=2; i<=n; ++i) {
        if(mnprime[i]<i) {
            continue;
        }
        for(int j=2*i; j<=n; j+=i) {
            mnprime[j]=min(mnprime[j], i);
        }
    }
}
vector<int> factor(int val) {
    vector<int> out;
    while(val>1) {
        out.push_back(mnprime[val]);
        val/=mnprime[val];
    }
    return out;
}

Αποδείξεις

Απόδειξη του μικρού θεωρήματος του Fermat

Έστω ο πρώτος , και ο ακέραιος , έτσι ώστε . Θεωρούμε τα πρώτα πολλαπλάσια του , τα οποία είναι τα . Εάν υπάρχουν δύο μεταξύ τους τα οποία είναι ισοϋπόλοιπα του , τότε έχουμε ότι υπάρχει και έτσι ώστε , άτοπο. Παρομοίως δεν υπάρχει κανένα έτσι ώστε , διότι και για κάθε . Επομένως όλα αυτά τα πολλαπλάσια έχουν διαφορετικό υπόλοιπο με διαιρέτη τον , και αφού τα μη μηδενικά υπόλοιπα του είναι σε πλήθος, όπως και τα πολλαπλάσια, αναγκαστικά θα υπάρχει κάποια 1 προς 1 αντιστοιχία μεταξύ των πολλαπλασίων του και των υπολοίπων του . Άρα,

Απόδειξη της πολυπλοκότητας του κόσκινου του Ερατοσθένη

Θα χρησιμοποιήσουμε το εξής χωρίς απόδειξη: , όπου είναι το σύνολο των πρώτων. Για τον κάθε πρώτο , εξετάζουμε όλα του τα πολλαπλάσια μέχρι το . Επομένως, κάνουμε πράξεις για κάθε . Εάν αθροίσουμε αυτούς τους αριθμούς έχουμε ότι θα κάνουμε πράξεις.

Ένα άλλο χρήσιμο αποτέλεσμα γενικότερα για τα κόσκινα είναι ότι .

Προβλήματα

GAMES (Spoj)

NDIV (Spoj)

Prevdiv (23ος πδπ)

Strategy (2014 Jboi)

Couple Cover (Codeforces)

GCD Table (Codeforces)

Mashmokh and ACM (Codeforces)

Rare Easy Problem (UVa)

Play with Floor and Ceil (UVa)

Marbles (UVa)

Πηγές

http://codeforces.com/blog/entry/8989

http://e-maxx.ru/algo/diofant_2_equation (μέσω google translate)

Σχόλια