Considerăm următorul algoritm, aplicat pentru două numere naturale a și b:
R ← 0 ┌ cattimp a ≠ 0 executa │ ┌ daca a este impar atunci │ │ R ← R + b │ └■ │ a ← [a / 2] │ b ← b * 2 └■ scrie R
Dacă îl vom aplica pentru a = 18 și b = 12 vom constata că:
a |
b |
R |
Explicație | ||
|---|---|---|---|---|---|
18 |
12 |
0 |
a este par => R nu se modificăa se înjumătățește, b se dublează |
||
9 |
24 |
0 + 24 = 24 |
a este impar => la R se adună b = 24a se înjumătățește, b se dublează |
||
4 |
48 |
24 |
a este par => R nu se modificăa se înjumătățește, b se dublează |
||
2 |
96 |
24 |
a este par => R nu se modificăa se înjumătățește, b se dublează |
||
1 |
192 |
24 + 192 = 216 |
a este impar=> la R se adună b = 192a se înjumătățește, b se dublează |
||
0 |
384 |
216 |
a a devenit 0, ne oprim |
||
Observăm că rezultatul R = 216 este de fapt chiar 18 * 12. Aceasta nu este o coincidență!
Algoritmul determină rezultatul înmulțirii dintre a și b și se numește înmulțirea a la russe (înmulțirea rusească). În ciuda numelui, se pare că metoda era cunoscută în Egiptul Antic și poate fi descrisă astfel:
a și b:
a este impar, il adunăm pe b la rezultat, care inițial este 0;a se înjumătățește, iar b se dublează;a devine 0.Aparent ciudată, metoda se bazează de fapt pe scrierea unui număr ca sumă de puteri ale lui 2 ( sau reprezentarea numerelor în baza 2) – știm că orice număr natural are o unică reprezentare în baza 2, poate fi scris în mod unic ca sumă de puteri ale lui 2.
Să-l scrie pe \(a = 18\) ca sumă de puteri ale lui 2: \(18 = 2 + 16 = 0 \cdot 2^0 + 1 \cdot 2^1 + 0 \cdot 2^2 + 0 \cdot 2^3 + 1 \cdot 2^4\). 0 1 0 0 1 sunt cifrele reprezentării lui 18 în baza 2, în ordine inversă (ordinea în care sunt determinate, prin metoda cunoscută ca resturi ale împărțirii la 2).
Atunci:
$$\begin{align}
18 \cdot 12 & = \left(0 \cdot 2^0 + 1 \cdot 2^1 + 0 \cdot 2^2 + 0 \cdot 2^3 + 1 \cdot 2^4 \right) \cdot 12 \\
& = \left(0 \cdot 1 + 1 \cdot 2 + 0 \cdot 4 + 0 \cdot 8 + 1 \cdot 16 \right) \cdot 12 \\
& = 0 \cdot 12 + 1 \cdot 24 + 0 \cdot 48 + 0 \cdot 96 + 1 \cdot 192 \\
& = 24 + 192 \\
& = 216 \\
\end{align}$$
Observăm deci că valorile care se adună pentru a obține rezultatul sunt tocmai acele valori obținute prin dublările succesive ale lui b când a este impar – ceea ce corespunde cifrei binare 1!!
Acest modalitate de înmulțire este interesantă, dar nu este neapărat utilă în practică. Mult mai interesant este următorul algoritm pentru ridicarea la putere, care poate fi utilizat în rezolvarea de probleme de informatică.
Să considerăm \(A^{25}\). Îl vom scrie pe \(25\) ca sumă de puteri ale lui \(2\): \(25 = 1 + 8 + 16\).
Atunci \( A^{25} = A^{1 + 8 + 16} = A^1 \cdot A^8 \cdot A^{16} = \underbrace{{(A^1)}^1}_{=A^1} \cdot \underbrace{{(A^2)}^0}_{=1} \cdot \underbrace{{(A^4)}^0}_{=1} \cdot \underbrace{{(A^8)}^1}_{=A^8} \cdot \underbrace{{(A^{16})}^1}_{=A^{16}}\). Observăm, desigur, că exponenții \(0\) și \(1\) sunt cifrele reprezentării în baza \(2\) a lui \(25\).
Pentru a determina An procedăm astfel:
P, format din factori de forma A1, A2, A4, A8, …2 a lui n, începând cu cea mai nesemnificativă:
1, înmulțim pe A la P, P ← P * A;A cu el însuși, A ← A * A;, obținând următoarea putere din șirul de mai susUrmătorul program pseudocod descrie algoritmul de mai sus:
citeste A,n (baza, exponent) P ← 1 ┌ cattimp n ≠ 0 executa │ c ← n % 2 │ ┌ daca c = 1 atunci │ │ P ← P * A │ └■ │ n ← [n / 2] │ A ← A * A └■ scrie P
Observăm că algoritmul este foarte eficient! Numărul de iterații este egal cu numărul de cifre din reprezentarea în baza 2 a lui n – mult mai mic decât n!
Operațiile logice lucrează cu valori de adevăr. Le folosim instinctiv în viața de zi cu zi, dar uneori ne pun în dificultate atunci când trebuie să le aplicăm într-un algoritm.
Operațiile logice se fac cu valori de adevăr (notate TRUE / FALSE, sau ADEVĂRAT / FALS) sau propoziții care au ca rezultat valori de adevăr. De exemplu:
ADEVĂRAT;FALS.Exemple din matematică:
19 este impar – ADEVĂRAT10 se divide cu 3 – FALS3 ≠ 4 – ADEVĂRAT10 < 5 – FALSx > 3 – nu știm rezultatul; depinde de valoarea lui x!!Exemple din algoritmică (C/C++):
19%2==1 – ADEVĂRAT, în C/C++ rezultatul este 110%3==0 – FALS, în C/C++ rezultatul este 03 != 4 – ADEVĂRAT, în C/C++ rezultatul este 110 < 5 – FALS, în C/C++ rezultatul este 0x > 3 – nu știm rezultatul; depinde de valoarea lui x din momentul in care se face comparația!!De multe ori o propoziție logică conține variabile, iar valoarea de adevăr a ei depinde de valorile variabilelor.
Două propoziții logice, care depind de anumite variabile, sunt echivalente dacă, pentru orice valori ale variabilelor, sunt fie amândouă adevărate, fie amândouă false. De exemplu, propoziția “numărul natural n este par” este echivalentă cu propoziția “restul împărțirii numărului natural n la 2 este 0”.
Negarea este o operație foarte folosită în viața de zi cu zi. Prin negare, propoziția “Orașul Sibiu este în România.” devine “Orașul Sibiu nu este în România.”, care este FALSĂ. Prin negarea unei propoziții adevărate se obține o propoziție falsă, iar prin negarea unei propoziții false se obține o propoziție adevărată.
În C/C++, negarea este o operație unară, cu prioritate mare, iar operatorul său este !. Ea poate fi aplicată pentru orice valori numerice (și nu numai), dar de regulă se aplică asupra altor valori logice.
p este o expresie cu valoarea 0 (FALS), atunci !p are valoarea 1 (ADEVĂRAT).p este o expresie cu valoarea diferită de 0 (ADEVĂRAT), atunci !p are valoarea 0 (FALS).Exemple:
int x = 10; cout << !(x == 10); // 0: x == 10 este adevărat și are rezultat 1, 1 negat este 0 cout << !x == 10; // tot 0, dar se efectuează mai întâi !x, adică !10, cu rezultat 0, apoi 0 == 10, cu rezultat fals, adică 0 cout << !(x < 5); // 1: x < 5 este fals, adică 0, 0 negat este 1
Conjuncția realizează “compunerea” a două propoziții prin intermediul cuvântului ȘI. Exemple:
Astfel, conjuncția a două propoziții este ADEVĂRAT dacă ambele propoziții sunt adevărate, în toate celelalte cazuri este FALSĂ.
În C/C++ simbolul conjuncției este &&. La fel ca negarea, și conjuncția poate fi aplicată pentru orice valori numerice (și nu numai), dar de regulă se aplică asupra altor valori logice.
Fie p și q două valori numerice:
p este nenul și q este nenul, atunci (p&&q)==1;p este nenul și q este nul, atunci (p&&q)==0;p este nul și q este nenul, atunci (p&&q)==0;p este nul și q este nul, atunci (p&&q)==0;Exemple:
cout << (1 < 2 && 2 == 1 + 1); // 1; ADEVĂRAT ȘI ADEVĂRAT este ADEVĂRAT cout << (1 < 2 && 2 != 1 + 1); // 0; ADEVĂRAT ȘI FALS este FALS cout << (1 == 2 && 2 == 1 + 1); // 0; FALS ȘI ADEVĂRAT este FALS cout << (1 == 2 && 2 != 1 + 1); // 0; FALS ȘI FALS este FALS
Disjuncția realizează “compunerea” a două propoziții prin intermediul cuvântului SAU. Exemple:
Astfel, disjuncția a două propoziții este ADEVĂRATĂ dacă cel puțin una dintre ele este adevărată; dacă ambele propoziții sunt false, disjucția lor este FALSĂ.
În C/C++ simbolul disjuncției este ||. La fel ca negarea și conjuncția, și disjuncția poate fi aplicată pentru orice valori numerice (și nu numai), dar de regulă se aplică asupra altor valori logice.
Fie p și q două valori numerice:
p este nenul și q este nenul, atunci (p || q)==1;p este nenul și q este nul, atunci (p || q)==1;p este nul și q este nenul, atunci (p || q)==1;p este nul și q este nul, atunci (p || q)==0;Exemple:
cout << (1 < 2 || 2 == 1 + 1); // 1; ADEVĂRAT SAU ADEVĂRAT este ADEVĂRAT cout << (1 < 2 || 2 != 1 + 1); // 1; ADEVĂRAT SAU FALS este ADEVĂRAT cout << (1 == 2 || 2 == 1 + 1); // 1; FALS SAU ADEVĂRAT este ADEVĂRAT cout << (1 == 2 || 2 != 1 + 1); // 0; FALS SAU FALS este FALS
7 11 17 19 28 509
Acestea stabilesc niște reguli legate de situația în care aplicăm operația de negare asupra unor conjuncții și disjuncții. Formal, ele se exprimă astfel:
Fie p și q două expresii logice. Atunci:
!(p && q) ↔ !p || !q!(p || q) ↔ !p && !qPrin ↔ se înțelege echivalența a două propoziții.
Fie n un număr natural. Dorim o condiție care să fie adevărată dacă și numai dacă n are exact două cifre. Această condiție este: “mai mare sau egal cu 10 și mai mic decât 100”. Expresia C/C++ este: n >= 10 && n < 100.
Care este condița inversă? Adică, cum exprimăm faptul că n nu are exact două cifre? Dacă n nu are exact două cifre înseamnă că n are o cifră sau n are cel puțin trei cifre. Adică “este mai mic decât 10 sau mai mare sau egal cu 100”. În C/C++: n < 10 || n >= 100.
Adică !(n >= 10 && n < 100) este echivalent cu n < 10 || n >= 100.
Atenție: nu există nicio valoare pentru care n < 10 && n >= 100.
15 18 61 476 529
Se dă un tablou A cu n elemente întregi. Să se determine o secvență pentru care suma elementelor este maximă.
Problema este una clasică și admite diverse soluții. Vom vedea în continuare trei soluții, de complexități diferite! Să observăm însă că, dacă elementele ar fi pozitive, atunci secvența cerută ar fi întreg tabloul.
În continuare vom numi secvență candidat o secvență care ar putea fi secvența cerută.
Prima soluție are complexitatea \(O(n^3)\) și poate fi folosită când valoarea lui n este în jur de 100.
Fiecare secvență a vectorului poate fi secvență candidat. Vom identifica toate secvențele delimitate de indicii i j, cu 1 ≤ i ≤ j ≤ n. Pentru fiecare secvență, vom determina suma elementelor care o compun și vom reține secvența de sumă maximă:
int st = 0 , dr = 1 , Smax = -2000000000 , S;
for(int i = 1 ; i <= n ; ++ i)
for(int j=i;j<=n;++j)
{
S = 0;
for(int k = i ; k <= j ; ++ k)
S += A[k];
if(S > Smax)
Smax = S, st = i, dr = j;
}
cout << Smax << endl;
cout << st << " " << dr;
Soluția anterioară poate fi îmbunătățită folosind sume partiale pentru a determina suma elementelor din secvența i j. În acest mod complexitatea scade la \(O(n^2)\) și poate fi folosită când valoarea lui n este în jur de 1000.
SP[0] = 0;
for(int i =1 ; i <= n ; i ++)
SP[i] = SP[i-1] + A[i];
int st = 0 , dr = 1 , Smax = -2000000000 , S;
for(int i = 1 ; i <= n ; ++ i)
for(int j=i;j<=n;++j)
{
S = SP[j] - SP[i-1];
if(S > Smax)
Smax = S, st = i, dr = j;
}
cout << Smax << endl;
cout << st << " " << dr;
Ultima soluție, de complexitate liniară – \(O(n)\) pleacă de la ideea că dacă într-o secvență (PQ), formată prin reuniunea (lipirea) secvențelor (P) și (Q), suma elementelor din secvența (P) este negativă, atunci suma elementelor din sevența (Q) este mai mare decât suma elementelor din secvența (PQ). În cest caz secvența (PQ) nu mai este secvență candidat, dar (Q) este (probabil) secvență candidat.
Procedăm astfel:
A[i] la S – acesta reprezentând suma elementelor dintr-o secvență candidat;S este mai mare decât suma maximă, actualizăm rezultatele;S devine negativ, îl reinițializăm la 0; tot acum reinițializăm și poziția de început a secvenței candidat.int st , dr, Smax = -2000000000 , S = -1, start;
for(int i = 1 ; i <= n ; ++ i)
{
if(S < 0)
S = 0, start = i;
S += A[i];
if(S > Smax)
Smax = S, st = start, dr = i;
}
cout << Smax << endl;
cout << st << " " << dr;
Soluția de mai sus poate fi utilizată pentru valori mai mari ale lui n, de exemplu 100000 sau 1000000. De asemenea, poate fi ușor modificată încât să se evite folosirea tabloului, determinând rezultatul direct din citire. Astfel, complexitatea spațiu a algoritmului devine \(O(1)\).
Operațiile standard de intrare/ieșire se fac cu tastatură și ecranul, dar este posibil să realizăm și citiri din fișiere text, respectiv scrieri în fișiere text. Pentru a realiza operațiile propriu-zise, fișierele sunt asociate cu fluxuri de date, iar operațiile sunt similare cu cele cu tastatura și ecranul.
În C++ există mai multe modalități de lucru cu fișiere text. Toate respectă următoarele etape:
O modalitate uzuală de a deschide fișiere constă în declararea unor variabile de tip flux. Acestea sunt de tip:
ofstream pentru fluxurile de ieșire – asociate cu fișierele în care vom scrie;ifstream pentru fluxurile de intrare – asociate cu fișierele din care vom citi;Declararea variabilelor se poate face astfel:
ifstream fin(NUME_FISIER_INTRARE); ofstream fout(NUME_FISIER_IESIRE);
NUME_FISIER_INTRARE și NUME_FISIER_IESIRE sunt șiruri de caractere care conțin numele fișierelor din care se face citirea/în care se face scrierea, de exemplu:
ifstream fin("fisier.in");
ofstream fout("fisier.out");
Nu este obligatoriu să folosim extensiile .in și .out, dar ele sunt frecvent folosite în algoritmică, pentru a desemna fișierul de intrare (INput), respectiv fișierul de ieșire (OUTput.)
fin și fout sunt identificatori de variabile. Putem folosi orice identificator, dar alegerea unor nume clare precum fin și fout, sau is (input stream) și os (output stream) fac, considerăm noi, programele mai ușor de înțeles și depanat.
Secvența de mai sus realizează simultan două operații: declararea variabilei de tip flux și dechiderea acestuia (asocierea cu fișierul corespunzător). Ele pot fi realizate și independent, de exemplu astfel:
ifstream fin;
fin.open("fisier.in");
sau
fstream fin("fisier.in", ios::in), fout("fisier.out", ios::out);
Declararea variabilelor de tip flux se poate face oriunde, cu respectarea restricțiilor cunoscute: orice variabilă folosită trebuie să fi fost anterior declarată.
Pentru citirea propiu-zisă a datelor din fișier/scrierea datelor în fișier se folosesc operatorii de extracție din flux/inserare în flux.
De exemplu:
int x; fin >> x; fout << 2 * x;
Se face astfel:
fin.close(); fout.close();
Următorul program este soluție corectă pentru problema #sum :
#include <iostream>
#include <fstream>
using namespace std;
ifstream fin("sum.in");
ofstream fout("sum.out");
int main()
{
int a , b, s;
fin >> a >> b;
fin.close();
s = a + b;
fout << s;
fout.close();
return 0;
}
fin >> ...) vor eșua și comportamentul programului devine de multe ori impredictibil;Secvențele escape au fost folosite inițial în limbajele C/C++; în prezent sunt folosite în numeroase alte limbaje, precum Java, C# sau PHP. Ele sunt succesiuni de caractere care cu alt înțeles decât cel direct atunci când sunt folosite într-un literal caracter sau șir de caractere, fiind înlocuite cu caractere care nu ar putea fi folosite în mod direct.
Toate secvențele escape încep cu caracterul \ – numit caracter escape și conțin două sau mai multe caractere. Cele care urmează după \ definesc caracterul care va apărea în constanta literal. De exemplu, \n este o secvență escape care reprezintă caracterul newline – trecere la rând nou.
Să scriem un program care să afișeze pe ecran textul Hamlet a spus "A fi, sau a nu fi". – atenție la ghilimele! După cum știm, un literal de tip șir de caractere este delimitat cu caracterul ghilimele ", deci ar trebui să folosim o instrucțiune C++ de felul următor:
cout << "Hamlet a spus "A fi, sau a nu fi".";
Această instrucțiune este însă greșită: compilatorul identifică șirul "Hamlet a spus ", iar șirul A fi, sau a nu fi nu are înțeles sintactic! Pentru a fi corect vom folosi pentru caracterul " din șir o secvență escape, adică \":
cout << "Hamlet a spus \"A fi, sau a nu fi\".";
Mai sus, secvența escape \" este pentru caracterul " – acesta având alt înțeles pentru compilator.
| _ Secvența escape | _ Cod ASCII | _ Caracter |
|---|---|---|
\a |
7 |
Alertă, Beep |
\b |
8 |
Backspace |
\e |
27 |
Escape |
\f |
12 |
Formfeed |
\n |
10 |
NewLine |
\r |
13 |
Carriage Return |
\t |
9 |
Tab orizontal |
\v |
11 |
Tab vertical |
\\ |
92 |
Backslash |
\" |
34 |
Ghilimele |
\' |
39 |
Apostrof |
\? |
63 |
Semn de întrebare |
\0 |
0 |
Caracterul nul, cu înțeles special în șirurile de caractere |
\nnn |
oricare | Caracterul cu codul egal cu valoarea octală nnn |
\xhh |
oricare | Caracterul cu codul egal cu valoarea hexazecimală hh |
Există de asemenea secvențe escape pentru reprezentarea caracterelor non ASCII.
Utilizarea ghilimelelor într-un șir de caractere
#include <iostream>
using namespace std;
int main()
{
cout << "Hamlet a spus \"A fi, sau a nu fi\".";
return 0;
}

Trecerea la rând nou
#include <iostream>
using namespace std;
int main()
{
cout << "Vrei 10 la info?\nHai pe pbinfo";
return 0;
}

Secvența escape poate fi folosită într-un caracter (ex. '\n') sau într-un șir de caractere (ex. "\n"). Următoarele două programe au același efect ca cel de mai sus:
#include <iostream>
using namespace std;
int main()
{
cout << "Vrei 10 la info?" << "\n" << "Hai pe pbinfo";
return 0;
}
#include <iostream>
using namespace std;
int main()
{
cout << "Vrei 10 la info?" << '\n' << "Hai pe pbinfo";
return 0;
} Ciurul lui Eratostene este o metodă rapidă prin care stabilește despre fiecare număr natural mai mic sau egal cu un n dat dat este prim sau nu. Valoarea lui n trebuie să fie suficient de mică pentru a putea declara un tablou P cu n+1 elemente, un element P[k] având valoarea 1 dacă k este prim, respectiv valoarea 0 dacă k nu este prim.
Următoarea secvență C++ construiește tabloul P[]:
int P[n+1];
for(int i = 0 ; i <= n ; i ++)
P[i] = 1;
P[0] = P[1] = 0;
for(int i = 2 ; i * i <= n ; i ++)
if(P[i] == 1)
for(int j = 2 ; i * j <= n ; j ++)
P[i * j] = 0;
Ideea de mai sus poate fi utilizată pentru a determina diverse rezultate în care intervin divizorii unui număr, rezultate calculate pentru toate numerele mai mici sau egale cu n:

Nr[]. La final, Nr[x] va fi numărul de divizori ai lui x;Nr[] cu 0;Nr[i] mărim valorile elementelor Nr[k], unde k = i * j este multiplu al lui i – dacă i este divizor al lui k, atunci trebuie numărat ca atare.int Nr[DIM + 1];
for(int i = 1 ; i <= DIM ; i ++)
Nr[i] = 0;
for(int i = 1; i <= DIM ; i ++)
for(int j = 1 ; i * j <= DIM ; j ++)
Nr[i * j] ++;
OBS: Un număr x este prim dacă și numai dacă la final Nr[x] este egal cu 2.
S[]. La final, S[x] va reprezenta suma divizorilor lui x;S[] cu 0;S[i] mărim valorile elementelor S[k] cu i, unde k = i * j este multiplu al lui i – dacă i este divizor al lui k, atunci trebuie adunat la suma divizorilor lui k.int S[DIM + 1];
for(int i = 1 ; i <= DIM ; i ++)
S[i] = 0;
for(int i = 1; i <= DIM ; i ++)
for(int j = 1 ; i * j <= DIM ; j ++)
S[i * j] += i;
OBS: Un număr x este prim dacă și numai dacă la final S[x] este egal cu x+1.
M[]. La final, M[x] va reprezenta cel mai mic divizor (factor) prim al lui x;M[x] cu x – pentru numerele prime cel mai mic factor prim sunt ele însele;2 și pentru fiecare număr i prim (pentru care M[i] = i) modificăm, dacă este cazul, cel mai mic factor prim pentru toți multipli k = i * j lui i.int M[DIM + 1];
for(int i = 1 ; i <= DIM ; i ++)
M[i] = i;
for(int i = 2; i <= DIM ; i ++)
if(M[i] == i)
for(int j = 2 ; i * j <= DIM ; j ++)
if(M[i * j] == i * j)
M[i * j] = i;
OBS:
x este prim dacă și numai dacă la final M[x] este egal cu x.i, vom modifica multiplul k = i * j numai dacă M[k] este încă k. De exemplu, pentru i=3, nu-l mai modifică pe M[6] – a fost deja modificat pentru i=2.M[k] de pentru fiecare i, deoarece i este considerat în ordine crescătoare, la final M[k] va memora cel mai mare divizor prim al lui k. Următoarea secvență obține acest rezultat – observați că modificările sunt minore:int M[DIM + 1];
for(int i = 1 ; i <= DIM ; i ++)
M[i] = i;
for(int i = 2; i <= DIM ; i ++)
if(M[i] == i)
for(int j = 2 ; i * j <= DIM ; j ++)
M[i * j] = i;
Indicatorul lui Euler este tratat în acest articol. Reluăm aici numai secvența C/C++:
int F[DIM + 1];
for(int i =1 ; i <= DIM ; i ++)
F[i] = i;
for(int i = 2; i <= DIM ; i ++)
if(F[i] == i)
{
for(int j = 1 ; j * i <= DIM ; j ++)
F[j * i]= F[j * i] / i * (i - 1);
}
OBS: Un număr x este prim dacă și numai dacă la final F[x] este egal cu x-1.
Încercați să determinați în același mod:
Operațiile pe biți, descrise aici sunt foarte rapide și au o serie de aplicații utile în practică. Acesta articol prezintă o parte dintre ele, limbajul de programare folosit fiind C/C++.
Reamintim faptul că biții din reprezentarea internă a unui număr întreg se numerotează începând de la 0, de la dreapta spre stânga.
2Înmulțirea cu o puterea lui 2 se face foarte rapid cu operația de deplasare la stânga pe biți, <<. Astfel, a * 2k este egal cu a << k. Desigur, atenție la overflow!
2Câtul împărțirii poate fi determina prin deplasarea la dreapta, >>. Astfel, a / 2k este egal cu a >> k.
Reprezentarea în baza 2 a unui număr par (și reprezentarea sa internă) se termină cu cifra 0, iar a unui număr impar se termină cu 1. Atunci, deoarece reprezentarea lui 1 are un singur bit 1, restul fiind 0, operația n & 1 are rezultat:
0, dacă n este par1, dacă n este imparÎN general n & 1 reprezintă ultimul bit din reprezentarea internă a lui n.
2Puterile lui 2 au un singur bit 1, ceilalți fiind 0. Mai clar, 2k are doar bitul k egal cu 1, ceilalți fiind 0. În plus, 2k-1 are toate cifrele binare 1 – de fapt, primele k cifre (de la dreapta) sunt 1, celelalte fiind 0. Observăm că aplicăm operația & între 2k și 2k-1 vom obține 0.
Pentru a verifica dacă un număr natural oarecare n este putere a lui 2, calculăm expresia n & (n-1). Rezultatul său este 0 dacă și numai dacă n este putere a lui 2.
În general, n & (n-1) are ca rezultat valoarea lui n în care ultimul bit 1 a fost transformat în 0. Dacă n este putere a lui 2, în rezultatul expresiei anterioare singurul bit 1 al lui n devine 0, deci rezultatul este 0.
2 care îl divide pe nEste egală cu 2 la puterea numărul de biți 0 de la sfârșitul reprezentării în baza 2 a lui n. Acest număr poate fi determinat rapid ca rezultat al expresiei n & -n. Analizați reprezentările interne a lui n și a lui -n pentru a înțelege mai bine!
1Fie n un număr întreg (de regulă natural), iar k un număr natural. Ne propunem să transformăm bitul k al lui n în 1, operație numită și setare a bitului k, ceilalți biți rămânând nemodificați – considerăm că valoarea lui k este mai mică decât numărul de cifre binare din reprezentarea internă a lui n (16 pentru short și unsigned short, 32 pentru int și unsigned int, etc.).
Transformarea unui bit în 1, precum și următoarele doua aplicații din acest articol (transformarea unui bit în 0 și determinarea valorii unui bit) se fac aplicând o anumită operație pe biți între numărul n și o mască, determinată convenabil pe baza valorii lui k.
În cazul transformării bitului k în 1, masca va avea doar bitul k egal cu 1, restul biților fiind 0. Astfel, masca este 1 << k, iar operația este n | (1 << k). Rezultatul ei are aceeași reprezentare internă ca n, cu excepția bitului k, care este transformat în 1; dacă bitul k era de la început 1, el nu se va modifica.
Mai precis, setarea bitului k se face prin următoarea atribuire: n = n | (1 << k);.
0Transformarea bitului k a lui n în 0, numită și resetarea bitului k, se face folosind o mască în care toți biții sunt 1, cu excepția bitului k sunt 1, bitul k fiind 0. Această mască este: ~(1 << k), ~ fiind operația de complementare a biților.
Atunci, resetarea bitului k a lui n se poate face cu atribuirea: n = n & ~(1 << k);.
Pentru a determina bitul k al lui n putem folosi expresia (n >> k) & 1. Prin operația n >> k se elimină ultimele k cife binare ale lui n, iar (n >> k) & 1 reprezintă ultimul bit al lui n >> k, deci bitul k al lui n.
O altă variantă ar fi să folosim masca 1 << k, prin operația n & (1 << k). Rezultatul acestei operații este 0, dacă bitul k este 0, respectiv 2k (adică 1 << k), dacă bitul k este 1.
^Disjuncția exclusivă are o proprietate interesantă, și anume că n ^ n este 0, indiferent de valoarea lui n. Acest lucru are câteva consecințe:
a ^ b ^ b este egal cu a. Putem realizat astfel un mecanism de cripare:
n un număr care trebuie criptat și k un număr care reprezintă cheia de criptare;n ^ k, prin care obținem numărul criptat c;c ^ k, prin care obținem numărul inițial n;x și una singură apare de un număr impar de ori (celelalte apărând de număr par de ori), o putem determina calculând suma XOR a numerelor x, adică:
S = 0;x
S ^= x;S.a și b, fără a folosi o variabilă suplimentară:
a = a ^ b;b = a ^ b;a = a ^ b;
Indicatorul lui Euler sau funcția lui Euler, sau totient se notează cu \( \varphi(n) \) (unde \(n\) este un număr natural nenul ) și reprezintă numărul de numere mai mici sau egale cu \(n\) și prime cu acesta.
Valoarea lui \( \varphi(n) \) poate fi determinată prin numărarea valorilor prime cu \(n\), sau putem aplica următoarea proprietate:
Proprietate: Pentru un număr natural \(n\) care are descompunerea în factori primi: \( n = p_{1}^{e_1} \cdot p_{2}^{e_2} \cdot … \cdot p_{k}^{e_k} \), are loc relația: \( \varphi(n)=(p_{1}-1)p_{1}^{e_{1}-1} \cdot(p_{2}-1)p_{2}^{e_{2}-1} \cdot \cdots \cdot (p_{k}-1)p_{k}^{e_{k}-1} \).
O scriere echivalentă este: \(\varphi(n)=n \left(1-\frac{1}{p_{1}}\right) \cdot \left(1-\frac{1}{p_{2}}\right) \cdot \cdots \cdot \left(1-\frac{1}{p_{k}}\right) \)
Exemplu: Pentru \( n = 12\), numerele mai mici decât \( n \), prime cu acesta sunt: \( \text 1, 5, 7, 11\), adică \( 4 \) numere.
Descompunerea în factori este: \( n = 12 = 2^2 \cdot 3^1 \).
Aplicând formula de mai sus obținem \( \varphi(12) = (2-1) \cdot 2^{2-1} \cdot (3-1) \cdot 3^{1-1} = 1 \cdot 2^1 \cdot 2 \cdot 3^0 = 2 \cdot 2 = 4 \).
Observaţie: Dacă \( n \) este număr prim, atunci \( \varphi(n) = n – 1 \).
Teorema lui Euler:
Dacă \(a, n\) sunt două numere naturale prime între ele, atunci:
$$ a^{ \varphi (n) } \equiv 1 \left(\mod n \right) $$
Următoarea funcție C++ determină valoarea indicatorului lui Euler pentru o valoare n transmisă ca parametru:
int phi(int n)
{
int r = n , d = 2;
while(n > 1)
{
if(n % d == 0)
{
r = r / d * (d - 1);
while(n % d == 0)
n /= d;
}
d ++;
if(d * d > n)
d = n;
}
return r;
}
Complexitatea ei este \(O \left( \sqrt{n} \right) \).
nUneori – vezi problema #Tramvaie – trebuie să determinăm indicatorul lui Euler pentru multe numere, acestea fiind relativ mici (de exemplu, mai mici decât 1.000.000). În această situație se poate utiliza un algoritm similar cu Ciurul lui Etatostene, bazat tot pe formula de mai sus: \( \varphi(n)=n \left(1-\frac{1}{p_{1}}\right) \cdot \left(1-\frac{1}{p_{2}}\right) \cdot \cdots \cdot \left(1-\frac{1}{p_{k}}\right) \). În plus, dacă \( p \) este număr prim, atunci \( \varphi(p) = p -1 \).
Algoritmul este următorul, pentru a determina valoarea indicatorului lui Euler pentru toate numerele mai mici sau egale cu DIM:
F[] cu DIM + 1 elemente; la final, F[x] va reprezenta indicatorul lui Euler pentru xF[i] = i; scopul acestei inițializari este dublu:
n produsul începe cu n;F[p] este egal cu p, deducem că p este număr prim și va influența indicatorul lui Euler pentru toți multipli săi.p numerele de la 2 la DIM:F[p] este egal cu p, înseamnă că p este număr prim:
F[p], aducându-l la valoarea corectă a indicatorului pentru numere prime; (A)k ai lui p mai mari decât acesta și actualizăm valoarea lui \( F[k] = F[k] * (1 – \frac{1}{p})\). (B)OBS: Pașii (A) și (B) pot fi integrați într-un singur pas de tip (B). Cum?
int F[DIM + 1];
for(int i =1 ; i <= DIM ; i ++)
F[i] = i;
for(int i = 2; i <= DIM ; i ++)
if(F[i] == i)
{
F[i] --;
for(int j =2 ; j * i <= DIM ; j ++)
F[j * i]= F[j * i] / i * (i - 1);
}

Standard Template Library – STL conține două funcții extrem de utile care realizează căutarea binară, lower_bound și upper_bound. Ele se găsesc în headerul algorithm și realizează căutarea atât pentru vectori STL cât și pentru tablourile unidimensionale standard C. În ambele cazuri elementele structurii de date trebuie să fie ordonate după un anumit criteriu, iar funcțiile au următoarele rezultate:
lower_bound – cel mai din stânga element care este mai mare sau egal cu valoarea căutată;upper_bound – cel mai din stânga element care este strict mai mare decât valoarea căutată.Observație: STL conține și funcția binary_search, care caută într-o structură de date o anumită valoare și returnează true dacă o găsește și false în caz contrar. Funcția lower_bound furnizează informații suplimentare, fiind astfel mai utilă în practică.
Antetele funcțiilor sunt:
Iterator lower_bound(Iterator p, Iterator u, Valoare v);Iterator lower_bound(Iterator p, Iterator u, Valoare v, Comparator fcmp);Valoare este un tip de date oarecare pentru care este definită relația de ordine < sau funcția comparator fcmp, iar p și u sunt iteratori la elemente ale unui vector STL de tip vector<Valoare>.
Funcția caută în secvența [p,u) cel mai din sțânga element mai mare sau egal cu v și returnează iterator la acel element, sau u, dacă nu există un asemenea element nu există. Dacă elementele vectorului sunt ordonate după un alt criteriu decât <, se folosește o funcție de comparare fcmp.
Iterator upper_bound(Iterator p, Iterator u, Valoare v);Iterator upper_bound(Iterator p, Iterator u, Valoare v, Comparator fcmp);Funcția caută în secvența [p,u) cel mai din sțânga element mai mare strict decât v și returnează iterator la acel element, sau u, dacă nu există un asemenea element nu există. Dacă elementele vectorului sunt ordonate după un alt criteriu decât <, se folosește o funcție de comparare fcmp.
În funcțiile de mai sus, numele parametrilor au următoarea semnificație:
p – primulu – ultimulv – valoarea de căutatf – comparator (funcție de comparare)Important: Elementul u nu face parte din secvența în care se caută valoarea v.
vector<int> A;
int k;
....
auto I = lower_bound(A.begin(), A.end(), k); // I este iterator
if(I == A.end() || *I != k)
cout << k << " nu apare în vector";
else
cout << k << " apare în vector pe poziția " << I - A;
Antetele funcțiilor sunt:
Valoare * lower_bound(Valoare * p, Valoare * u, Valoare v);Valoare * lower_bound(Valoare * p, Valoare * u, Valoare v, Comparator fcmp);Valoare este un tip de date oarecare pentru care este definită relația de ordine < sau funcția comparator fcmp, iar p și u sunt pointeri la Valoare șî reprezintă adresele unor elemente ale unui tablou declarat Valoare A[dim];.
Funcția caută în secvența [p,u) cel mai din sțânga element mai mare sau egal cu v și returnează pointer la acel element, sau u, dacă nu există un asemenea element nu există. Dacă elementele vectorului sunt ordonate după un alt criteriu decât <, se folosește o funcție de comparare fcmp.
Iterator upper_bound(Iterator p, Iterator u, Valoare v);Iterator upper_bound(Iterator p, Iterator u, Valoare v, Comparator fcmp);Funcția caută în secvența [p,u) cel mai din sțânga element mai mare strict decât v și returnează pointer la acel element, sau u, dacă nu există un asemenea element nu există. Dacă elementele vectorului sunt ordonate după un alt criteriu decât <, se folosește o funcție de comparare fcmp.
#include <iostream>
#include <algorithm>
int main()
{
int n = 10, v[]={10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; // indexare de la zero
//lower_bound: cel mai din stânga iterator i pentru care v[i] >= x
std::cout << std::lower_bound(v , v + n , 20) - v << std::endl; // 1, v[1] >= 20
std::cout << std::lower_bound(v , v + n , 25) - v << std::endl; // 2, v[2] >= 25
//upper_bound: cel mai din stânga iterator i pentru care v[i] > x
std::cout << std::upper_bound(v , v + n , 20) - v << std::endl; // 2, v[2] > 20
std::cout << std::upper_bound(v , v + n , 25) - v << std::endl; // 2, v[2] > 25
return 0;
}
int A[1000], n; // indexat de la 0 la n - 1
int k;
....
int * q = lower_bound(A, A + n, k); // q este pointer
if(q == A + n || *q != k)
cout << k << " nu apare în tablou";
else
cout << k << " apare în tablou la indicele " << q - A;
Dacă elementele vectorului (tabloului) satisfac altă relație de ordine, va trebui să definim o funcție de comparare, fcmp, sau să folosim un predicat din STL. De exemplu, dacă vectorul (tabloul) are elementele în ordine descrescătoare putem folosi greater. De exemplu:
int A[1000], n; // indexat de la 0 la n - 1 int k; .... int * q = lower_bound(A, A + n, k, greater<int>());
În cazul unei relații de ordine mai complexe, se poate defini o funcție de comparare, în felul următor:
bool fcmp(Valoare A, Valoare B);
Funcția are doi parametru de același tip cu elementel tabloului (vectorului) în care face căutarea și va returna true, dacă primul parametru, A, este strict înaintea lui B în ordinea dorită și false în caz contrar.
Uneori interfața CodeBlocks suferă modificări, lipsind anumite componente ale ferestrei. De exemplu, poate să aibă aspectul următor:

Acest articol prezintă modul în care afișam sau ascundem cele mai importante/frecvent utilizate componente ale interfeței CodeBlocks.

Panoul Manager este important deoarece aici sunt afișate fișierele care fac parte din proiectul CodeBlocks.
Afișarea lui poate fi oprită/realizată din meniul View->Manager sau scurtătura Shift+F2.

Panoul Logs este important deoarece aici sunt afișate erorile de sintaxă și avertismentele generate la compilarea programului C++.
Afișarea lui poate fi oprită/realizată din meniul View->Logs sau scurtătura F2.

Dacă avem mai multe fișiere deschise (situație tipică în cazul programelor care fac operațiile de intrare/ieșire cu fișiere) folosim pentru trecerea de la un document la altul tab-urile afișate deasupra zonei de editare.
Afișarea tab-rilor de editare poate fi oprită/realizată din meniul View->Hide editor tabs.

Barele de unelte conțin butoane cu care accesăm rapid anumite opțiuni ale aplicației. Vizibilitatea lor este controlată prin:
Implicit panourile Manager și Logs sunt andocate (“lipite”) la marginea ferestrei CodeBlocks: marginea din stânga, respectiv de jos. Andocarea poate fi anulată, panourile devenind ferestre flotante pe desktop. Pentru a le reandoca la marginea ferestrei, trageți panoul de bara de nume spre marginea dorita a ferestrei și eliberați-o acolo.
Spor la codat!