Clasa a VI-a lecția 1 - 25 sep 2014
Lecție
Complexitatea algoritmilor
Complexitate: ce înseamnă, cîte operații pe secundă execută un calculator, ce operații sînt "scumpe".
Iată un exemplu:
Cifra k | Rezolvare |
---|---|
Calculați cifra k a unui număr n, numărînd cifrele de la dreapta la stînga. | while ( k > 1 ) {
n = n / 10;
k--;
}
cf = n % 10; Complexitate: proporțional cu numărul de cifre parcurse, k, adică O(k) |
Alt exemplu: afișați descompunerea în factori primi a numărului n. Exemplu: 1176 = 23 x 31 x 72. Optimizarea descompunerii în factori primi. Complexitățile aferente.
Ideea rezolvării constă în existenţa unui singur factor prim mai mare decât radicalul numărului. O implementare a acestei probleme de complexitate O(sqrt(n)):
#include <stdio.h>
int main() {
int n, div, exp;
scanf("%d", &n);
div = 2; //Incepem cautarea factorilor primi de la primul numar prim
while ( div * div <= n ) { //Cautam factorii primi pana la radicalul lui n
exp = 0;
while ( n % div == 0 ) {
n /= div;
exp++;
}
if (exp > 0)
printf("%d^%d\n", div, exp);
div++;
}
// daca n mai contine un factor, el este un numar prim la puterea unu
if ( n > 1 )
printf( "%d^1\n", n );
return 0;
}
Recapitulare cunoștințe informatică
Rezolvați următoarele exerciții. Pentru fiecare exercițiu rezolvat calculați complexitatea timpului de execuție (numărul de operații efectuat de algoritm în funcție de datele de intrare).
Numere cu două cifre
Spuneți dacă numărul n este format din exact două cifre repetate de oricâte ori. 23223 și 900990 sînt astfel de numere, pe cînd 593 și 44002 nu. Nu aveți voie să folosiți tablouri (vectori sau matrice).
Iată o metodă de rezolvare: eliminăm ultima cifra, c1, a numărului, iar apoi continuăm să eliminăm ultima cifră cîtă vreme este egală cu c1. Apoi eliminăm iarăși ultima cifră, c2, iar apoi continuăm să eliminăm ultima cifră cîtă vreme este egală cu c1 sau c2. Complexitatea este proporțională cu numărul de cifre al lui n, adică O(log n). Iată soluția bazată pe această idee:
#include <stdio.h>
int main() {
int n, c1, c2;
scanf("%d", &n);
c1 = n % 10; // retinem ultima cifra din numar in c1
n = n / 10; // si o eliminam din n
// eliminam toate aparitiile lui c1 din coada numarului
while ( n > 0 && n % 10 == c1 )
n = n / 10;
if (n == 0)
printf("NU"); // Daca numarul nu mai are alte cifre, afisam NU
else {
c2 = n % 10; // retinem urmatoarea cifra din numar in c2
n = n / 10;
// eliminam toate aparitiile lui c1 si c2 din coada numarului
while ( n > 0 && (n % 10 == c1 || n % 10 == c2) )
n = n / 10;
if ( n == 0 ) // daca numarul a avut exact doua cifre, acum va fi zero
printf("DA");
else
printf("NU");
}
return 0;
}
Putere
Calculați an în mod cît mai eficient (a și n numere naturale). Problema este cunoscută şi sub numele de ridicare la putere în timp logaritmic. Ideea din spatele acestei rezolvări este următoarea:
- Dacă n este par, atunci an = a2*n/2 = (a2)n/2
- Dacă n este impar, atunci n-1 este par și avem an = a * an-1 = a * a2*(n-1)/2 = a * (a2)(n-1)/2 = a * (a2)n/2
În formulele de mai sus am considerat că / este împărțirea întreagă din limbajul C. Se observă că indiferent de paritatea lui n, la fiecare pas al iterației putem transforma a în a * a și apoi putem împărți n la 2. Doar în cazurile cînd n este impar vom acumula valoarea curentă a lui a la produsul calculat. Complexitatea acestei soluţii va fi logaritmică, O(log n). Ce înseamnă acest lucru? Că timpul necesar calculului este proporţional cu un număr k, unde k este exponentul lui 2 astfel încît 2k=n.
Iată soluția bazată pe această idee:
#include <stdio.h>
int main() {
int a, n, p;
scanf( "%d%d", &a, &n );
p = 1;
while ( n > 0 ) {
if (n % 2 == 1)
p = p * a;
a = a * a;
n = n / 2;
}
printf( "%d", p );
return 0;
}
Secvență monotonă
Spuneți dacă o secvență de numere este monotonă. O secvență este monotonă dacă numerele ei sînt fie în ordine crescătoare fie în ordine descrescătoare. Nu aveți voie să folosiți tablouri (vectori sau matrice).
Complexitatea acestei soluții este liniară, egală cu numărul de elemente ale secvenţei: O(n).
#include <stdio.h>
int main() {
int n, a, b, i, cresc, monoton;
scanf( "%d%d%d", &n, &a, &b ); // n si primele doua numere
i = 2;
// Ignoram toate elemente egale de la inceput, pt a stabili monotonia sirului
while (i < n && a == b) {
scanf("%d", &b);
i++;
}
// Daca sirul este crescator, monoton = 1; Altfel, monoton = -1.
cresc = monoton = (a < b) ? 1 : -1;
// Cat timp nu am terminat si este pastrata monotonia initiala
while (i < n && monoton == cresc) {
a = b;
scanf("%d", &b);
if (a < b)
cresc = 1;
else if (a > b)
cresc = -1;
i++;
}
if (monoton == cresc)
printf("DA");
else
printf("NU");
return 0;
}
Paranteze
Dată o secvență de 0 și 1, unde 0 înseamnă paranteză deschisă '(', iar 1 înseamnă paranteză închisă ')', spuneți dacă secvența reprezintă o expresie corectă de paranteze și dacă da calculați numărul maxim de paranteze una într-alta. Exemplu: 0 1 0 0 1 0 1 1 este corectă și are factorul de imbricare de 2, pe cînd secvența 0 0 0 1 1 1 0 este incorectă. Nu aveți voie să folosiți tablouri (vectori sau matrice).
Putem rezolva problema observînd următoarele reguli valabile pentru orice expresie corectă:
- oriunde "rupem" expresia, în partea stîngă vom avea un număr de paranteze deschise mai mare sau egal cu cele închise
- la final numărul de paranteze închise trebuie să fie egal cu cele închise
Dacă ambele condiții se îndeplinesc expresia e corectă. Prima condiție trebuie îndeplinită pentru fiecare poziție de rupere.
#include <stdio.h>
int main() {
int n, a, i, nrdesc, max;
scanf("%d", &n);
max = nrdesc = i = 0;
while ( i < n && nrdesc >= 0 ) {
scanf( "%d", &a );
if ( a == 0 ) {
nrdesc++;
if ( nrdesc > max )
max = nrdesc;
} else
nrdesc--;
i++;
}
if ( nrdesc == 0 )
printf("Expresie corecta, factor de imbricare %d", max);
else
printf("Expresie incorecta");
return 0;
}
Temă de gîndire pentru acasă: cum generalizăm? Avem paranteze rotunde, pătrate și acolade, aceeași cerință.
Generalități
Să ne aducem aminte următoarele lucruri, căci vom avea nevoie de ele la tema:
- Declararea vectorilor cu inițializare.
- Dimensiunile diverselor tipuri: în code::blocks int are 4 octeți, long long are 8 octeți.
- Estimarea numărului care încape pe un număr dat de biți folosind formula 210 ≈ 103
Tema
- Cîți ani bisecți între anul a și anul b. Interval închis [a, b]
- Tema 1 clasa a 6a
- Opțional: problema cu ușile de la codechef: http://www.codechef.com/BTCD2012/problems/DOORS
Rezolvări aici [1]
Doors
There are N doors of a palace, all of which are operated by a set of buttons. One day, Alice, who is just 8 years old, gets access to these buttons. Having recently learnt the multiplication tables, she decides to press buttons in a particular order. First, she presses all the buttons that are multiples of 1. Next, she presses all buttons that are multiples of 2, then 3 and so on until N; after which she leaves.
Each press of button toggles the state of the door, i.e. a door that is open will be closed by the button press and opened if closed initially.
Given that all doors were closed initially, give the number of doors that are open after she leaves.
Input
The input contains several lines. First line contains the number 't', which represents the number of test cases that follow. This is followed by 't' lines of numbers which represent 'N'. 0 < t < 1000000 0 < N < 100000000
Output
For each input, output the number of doors that are open at the end.
Example
Input:
4 4 10 16 27
Output:
2 3 4 5
Time Limit: 3 sec
Rezolvări aici [2]