Documente Academic
Documente Profesional
Documente Cultură
Operațiile pe biți sunt operații foarte eficiente, deoarece ele lucrează direct cu biții din
reprezentările în memorie ale operanzilor. Înțelegerea lor presupune înțelegerea reprezentării în
memorie a datelor întregi.
Valorile întregi se reprezintă în memorie ca o secvență de biți (cifre binare, 0 și 1). Acestă
secvență poate avea 8, 16, 32 sau 64 de biți.
Reprezentarea în memorie a datelor de tip întreg se face în mod similar pentru toate tipurile cu
semn (char, short int, int, long long int) și similar pentru toate tipurile fără semn
(unsigned char, unsigned short int, unsigned int, unsigned long long
int).
În exemplele care urmează vom folosi tipurile reprezentate pe 16 biți: unsigned short
int, respectiv short int.
Tipul unsigned short int memorează valori mai mari sau egale cu 0. Acestea se
reprezintă în memorie astfel:
Astfel, valorile fără semn care se pot reprezenta pe 16 biți sunt cuprinse între 0 și 216-1, adică
0 și 65535.
0 se reprezintă 0000000000000000
65535 se reprezintă 1111111111111111
5 se reprezintă 0000000000000101
133 se reprezintă 0000000010000101
Tipul short int memorează atât valori pozitive, cât și valori negative. Astfel, dintre cei 16
biți disponibili, cel mai din dreapta (numit bit de semn) stabilește semnul numărului. Dacă acest
bit este 0, numărul este pozitiv, dacă acest bit este 1, numărul este negativ. Astfel, se pot
memora 32768 valori negative, de la -32768 la -1, și 32768 pozitive sau zero, de la 0 la
32767.
Reprezentarea numerelor pozitive se face exact ca mai sus: se transformă numărul în baza 2 și se
completează cu zerouri nesemnificative. Nu la fel se face reprezentarea numerelor întregi
negative. Această reprezentare se face conform pașilor următori:
Operatori pe biți
Operațiile pe biți se aplică numai datelor de tip întreg, și presupun manipularea directă a biților
din reprezentarea în memorie a operanzilor.
Operatorul de negație ~
Este un operator unar care are ca rezultat numărul obținut prin complementarea față de 1 a biților
din reprezentarea numărului inițial (biții 0 devin 1, biții 1 devin 0).
Exemplu:
~ 133 == -134
Este un operator binar care are ca rezultat numărul obținut prin conjuncția fiecărei perechi de biți
ce apar în reprezentare în memorie a operanzilor:
0 & 0 == 0
0 & 1 == 0
1 & 0 == 0
1 & 1 == 1
Exemplu:
0000000000001101 &
0000000010010111
Se obține:
0000000000000101, adică 5
Este un operator binar care are ca rezultat numărul obținut prin disjuncția fiecărei perechi de biți
ce apar în reprezentare în memorie a operanzilor:
0 | 0 == 0
0 | 1 == 1
1 | 0 == 1
1 | 1 == 1
Exemplu:
Să calculăm 13 | 151.
Se obține:
Este un operator binar care are ca rezultat numărul obținut prin disjuncția exclusivă fiecărei
perechi de biți ce apar în reprezentare în memorie a operanzilor:
0 ^ 0 == 0
0 ^ 1 == 1
1 ^ 0 == 1
1 ^ 1 == 0
Exemplu:
Să calculăm 13 ^ 151.
0000000000001101 ^
0000000010010111
Se obține:
Este un operator binar care are ca rezultat numărul obținut prin deplasare spre stânga a biților din
reprezentarea în memorie a primului operand cu un număr de poziții egal cu al doilea operand.
Să calculăm 13 << 3.
Reprezentarea lui 13 este 0000000000001101. Deplasând toți biții spre stânga cu 3 poziții se
obține: 0000000001101000, adică 104.
Este un operator binar care are ca rezultat numărul obținut prin deplasare spre dreapta a biților
din reprezentarea în memorie a primului operand cu un număr de poziții egal cu al doilea
operand.
Reprezentarea lui 133 este 0000000010000101. Deplasând toți biții spre dreapta cu 3 poziții
se obține: 0000000000010000 adică 16.
Probleme rezolvate
Rezolvare: Utilizăm operatorul &. Acesta are rol de testare a biţilor. Dacă n este impar, atunci
reprezentarea sa în baza 2 va avea cel mai din dreapta bit pe 1. De exemplu, n = 13 se scrie în
baza 2 ca 1101. Atunci 1101 & 1 = 1. Dacă n este par, atunci cel mai din dreapta bit va fi 0. De
exemplu, n = 14 se scrie în baza 2 ca 1110. Atunci 1110 & 1 = 0. Iată că pentru orice număr n,
expresia n & 1 furnizează ca rezultat cel mai din dreapta bit. Putem scrie atunci următoarea
secvenţă:
cin >> n ;
if (( n & 1 ) == 1) cout << "Numar impar" ;
else cout << "Numar par" ;
cin >> k ;
cout << (1 << k) ;
Rezolvare: Pentru determinarea câtului se utilizează deplasarea la dreapta cu 3 biţi (ceea ce este
echivalent cu împărţirea prin 23). Pentru rest se utilizează expresia n & 7 (unde 7 vine de la 23- 1
). Deoarece 7 = 1112 atunci toţi biţii lui n vor fi anulaţi, cu excepţia ultimilor 3 cei mai din
dreapta. Generalizearea se obţine imediat, înlocuind 23 cu 2k.
cin >> n ;
cout << "Catul este : " << (n >> 3) ;
cout << "Restul este : " << (n & 7) ;
cin >> n ;
if ( (n & (n-1)) == 0 ) cout << "n este putere a lui 2" ;
else cout << "n nu este putere a lui 2" ;
Rezolvare: Ne bazăm pe faptul că în memorie n este deja reprezentat în baza 2, deci trebuie să-i
afişăm biţii de la stânga la dreapta. Presupunând că n este reprezentat pe 16 biţi, pe aceştia îi
numerotăm de la dreapta la stânga cu numere de la 0 la 15. Pentru a obţine bitul de pe poziţia i (0
<= i <= 15), utilizăm expresia (n >> i) & 1. Nu rămâne decât să utilizăm expresia pentru
fiecare i între 0 şi 15.
cin >> n ;
for (int i=15 ; i >= 0 ; i--)
cout << ((n >> i) & 1) ;
6. Se consideră două numere naturale n şi i (0 <= i <= 15). Să se marcheze cu 1 bitul i al lui n.
Rezolvare: Vom seta valoarea 1 la bitul i, indiferent de valoarea memorată anterior (0 sau 1).
Pentru setare, utilizăm operatorul sau pe biţi. Expresia care realizează aceasta este n | (1 <<
i) . Să ne amintim dintr-un exerciţiu anterior că 1 << i înseamnă 2i. Expresia n | 2i nu va
modifica decât bitul i care ne interesează, restul rămânând nemodificaţi, datorită faptului că dacă
b este un bit atunci b | 0 este egal cu b.
7. Se consideră două numere naturale a şi b, ambele cuprinse între 0 şi 255. Se cere să se
memoreze cele două numere într-un întreg n reprezentabil pe 16 biţi fără semn (deci de tip
unsigned short).
Rezolvare: Cele două numere a şi b pot fi reprezentate pe 8 biţi. De aceea cei 16 biţi ai lui n sunt
suficienţi. Stocăm a pe primii 8 biţi ai lui n, iar pe b pe ultimii 8 biţi ai lui n. n = a * 256 +
b ; De asemenea, dacă se cunoaşte n, se pot obţine valorile lui a şi b astfel: a = n >> 8 ;
// sau a = n / 256
b = n & 255 ; // sau b = n % 256 ;
int n, masca;
n = 65 ;
masca = 100 ;
cout << "\nValoarea initiala a lui n : " << n ;
n = n ^ masca ;
cout<< "\nValoarea codificata a lui n : " << n ;
n = n ^ masca ;
cout<< "\nValoarea decodificata a lui n : " << n ;
În cadrul acestui articol vă vom prezenta câteva metode prin care programele se pot optimiza
dacă utilizăm eficient operaţiile pe biţi sau folosim operaţii pe biţi în locuri în care, la prima
vedere, nu s-ar părea că ar fi necesare.
De cele mai multe ori, atât în concursuri cât şi în viaţa de zi cu zi a programatorului, atunci când
implementăm o metodă care este folosită de o aplicaţie şi vrem să facem această metodă
eficientă ca timp de execuţie, suntem învăţaţi din şcoală (sau ar trebui sã fim învăţaţi, chiar dacă
unii dintre profesori consideră că cel mai important algoritm învăţat în liceu este backtraking-ul,
soluţia tuturor problemelor) să ne uităm la complexitatea algoritmului implementat şi dacă
observăm că în practică algoritmul este mai încet decât ne dorim noi să fie să încercăm să găsim
un algoritm cu un ordin de complexitate mai mic.
Nu trebuie să uităm că ceea ce numim complexitatea unui algoritm este o aproximare a vitezei
unui algoritm şi nu o măsură absolută. Pentru dimensiuni mici ale datelor, câteodată nu se
observă diferenţă dintre O(n) şi O(n log n) sau O(n3/2). Autorului i s-a întâmplat ca la
implementarea soluţiei unei probleme care în engleză se numeşte "Bottleneck Minimal Spanning
Tree" să observe că rezolvarea în O(m log m) (folosind algoritmul de găsire a arborelui parţial de
cost minim al lui Kruskal) să fie mai rapidă decât rezolvarea mai laborioasă în O(m) a acestei
probleme. Deci trebuie dată o atenţie egală cu cea acordată complexităţii algoritmului şi
dimensiunii factorilor constanţi care apar.
În continuare vom încerca să găsim soluţii mai rapide decât cele naive pentru unele operaţii de
bază (toate operaţiile vor fi implementate pentru întregi pozitivi reprezentaţi pe 32 de biţi).
Aplicaţia #1
Rezolvare
Rezolvarea naivă a acestei probleme ar consta în parcurgerea secvenţială a biţilor lui n. În
continuare vă prezint această rezolvare:
int count(long n) {
int num = 0;
return num;
Dacă ne uităm cu atenţie şi analizăm rezultatul operaţiei n & (n - 1) putem obţine o soluţie mai
bună. Să luăm un exemplu:
110111010100002 = n
110111010011112 = n - 1
110111010000002 = n & (n - 1)
Se vede clar de aici că efectul operaţiei n & (n - 1) este anularea celui mai nesemnificativ bit cu
valoarea 1.
int count(long n) {
int num = 0;
if (n)
return num;
Dar este acest algoritm mai rapid? Am testat pentru toate numerele de la 1 la 224 şi rezultatele au
fost 2,654 secunde folosind metoda naivă şi 0.821 folosind a doua metodă. Rezultatul mult mai
bun al celei de-a doua metode se bazează în principal pe faptul că ea execută un număr de paşi
egali cu numărul de biţi cu valoarea 1 din număr, deci în medie jumătate din numărul de paşi
efectuaţi de prima metodă.
Aplicaţia #2
Rezolvare
Din cele prezentate mai sus se pot determina două metode evidente:
int parity(long n) {
int num = 0;
return num;
int parity(long n) {
int num = 0;
if (n)
return num;
Să scriem algoritmul care reiese din acest exemplu, luând în considerare faptul că numărul n este
reprezentat pe 32 de biţi:
int parity(long n) {
return n;
Deşi constanta multiplicativa este mai mare, numărul de operaţii are ordin logaritmic faţă de
numărul de biţi ai unui cuvânt al calculatorului.
Aplicaţia #3
Să se determine cel mai puţin semnificativ bit de 1 din reprezentarea binară a lui n.
Rezolvare
Rezolvarea naivă se comportă bine în medie, deoarece numărul de cicluri până la găsirea unui bit
cu valoarea 1 este de obicei mic, dar din cele discutate mai sus putem găsi ceva mai bun.
Asa cum am arătat n & (n - 1) are ca rezultat numărul n din care s-a scăzut cel mai puţin
semnificativ bit. Folosind această idee obţinem următoarea funcţie:
int low1(long n) {
Exemplu:
110110002 = n
110101112 = n - 1
110100002 = n & (n - 1)
110110002 = n
000010002 = n ^ (n & (n - 1))
Această funcţie este foarte importantă pentru structura de date arbori indexaţi binar prezentată
într-un un articol mai vechi din GInfo.
Aplicaţia #4
Să se determine cel mai semnificativ bit cu valoarea 1 din reprezentarea binară a lui n.
Rezolvare
Putem aplica şi aici ideile prezentate mai sus: cea naivă şi cea cu eliminarea biţilor, dar putem
găsi şi ceva mai bun.
O abordare ar fi cea a căutării binare (aplicabilă şi în problema anterioară). Verificăm dacă partea
superioară a lui n este 0. Dacă nu este 0, atunci căutăm bitul cel mai semnificativ din ea, iar dacă
este, ne ocupăm de partea inferioară, deci reducem la fiecare pas problema la jumătate.
int high1(long n) {
long num = 0;
if (0xFFFF0000 & n) {
num += 16;
if (0xFF00 & n) {
num += 8;
if (0xF0 & n) {
num += 4;
if (12 & n) {
num += 2;
}
if (2 & n) {
n = (2 & n) >> 1;
num += 1;
Aplicaţia #5
Să se determine indexul celui mai semnificativ bit de 1 din reprezentarea binară a lui n.
Rezolvare
Metodele prezentate la rezolvarea problemei 4 pot fi folosite şi aici, dar vom prezenta o nouă
rezolvare. Să luăm următorul şir de operaţii pentru un exemplu:
n = 100000002
n = n | (n >> 1)
n = 110000002
n = n | (n >> 2)
n = 111100002
n = n | (n >> 4)
n = 111111112
Se observă că aplicând o secvenţă asemănătoare de instrucţiuni cu cea de mai sus putem face ca
un număr n să se transforme în alt număr care are un număr de biţi de 1 egal cu 1 plus indexul
celui mai semnificativ bit cu valoarea 1 din n. De aici algoritmul este următorul...
int indexHigh1(long n) {
n = n | (n >> 1);
n = n | (n >> 2);
n = n | (n >> 4);
n = n | (n >> 8);
n = n | (n >> 16);
return count(n) - 1;
Aplicaţia #6
Rezolvare
int isTwoPower(long n) {
return ( n & (n - 1) ) == 0 ;
Operaţiile prezentate mai sus sunt implementate în limbajele de asamblare, dar câteodată se
folosesc algoritmi naivi şi atunci o implementare inteligentă ajunge să fie mai rapidă decât
instrucţiunea din limbajul de asamblare. Pentru unele supercalculatoare aceste instrucţiuni sunt
instrucţiuni ale procesorului.
Se pot găsi alţi algoritmi mai rapizi decât cei de mai sus, dar de obicei găsirea unui algoritm mai
inteligent este mai grea decât folosirea unei abordări banale care aduce un câştig foarte mare:
preprocesarea.
Rezolvarea aplicaţiei #1
Determinăm, pentru numerele de la 0 la 216 - 1, un tablou num, în care num[i] este egal cu
numărul de biţi de 1 din reprezentarea binară a lui i. De aici obţinem soluţia...
int count(long n) {
Rezolvarea aplicaţiei #2
Determinăm, pentru numerele de la 0 la 216 - 1, un tablou par, în care par[i] este egal cu
paritatea numărului de biţi de 1 din reprezentarea binară a lui i. De aici obţinem soluţia...
int parity(long n) {
}
Rezolvarea aplicaţiei #3
Determinăm, pentru numerele de la 0 la 216 - 1, un tablou l, în care l[i] este egal cu cel mai
nesemnificativ bit de 1 din reprezentarea binară a lui i. De aici obţinem soluţia...
int low(long n) {
Rezolvarea aplicaţiei #4
Determinăm, pentru numerele de la 0 la 216 - 1, un tablou h, în care h[i] este egal cu cel mai
semnificativ bit de 1 din reprezentarea binară a lui i. De aici obţinem soluţia...
int high(long n) {
Rezolvarea aplicaţiei #5
Determinăm, pentru numerele de la 0 la 216 - 1, un tablou iH, în care iH[i] este egal cu indexul
celui mai semnificativ bit de 1 din reprezentarea binară a lui i şi este egal cu -1 pentru 0. De aici
obţinem soluţia...
int indexHigh(long n) {
if (iH[n >> 16] != -1) return iH[n >> 16] + 16;
Deşi ceea ce am prezentat mai sus sunt mai degrabă trucuri de implementare decât algoritmi
serioşi şi în general nu se acordă aşa mare importanţă detaliilor de genul acesta, câteodată
asemenea trucuri se pot dovedi importante mai ales în timpul concursurilor.
Un asemenea algoritm ar fi luat numai 64 de puncte deoarece restricţiile date erau mari (n ≤ 200,
m ≤ 2500). Putem rescrie condiţia M[i][k] == 1 && M[j][k] == 1 ca şi M[i][k] ^ M[j][k] == 1,
deci putem modifica rezolvarea anterioară în modul următor...
pentru fiecare i
L = M[i] ^ M[j];
sfârşit pentru
sfârşit pentru
numaraBitiUnu(L)
Dacă în loc să păstrăm în M[i][j] un element al matricei binare, păstrăm 16 elemente, atunci în
linia L = M[i] ^ M[j] se efectuează cel mult 2500/16 calcule în loc de 2500 de calcule. Acum, ce
a rămas de optimizat este metoda numaraBitiUnu. Dacă folosim preprocesarea, atunci această
metodă va fi compusă şi ea din 2500/16 calcule. Se observă că în acest fel am mărit viteza de
execuţie a algoritmului cu un factor de 16.
Alt exemplu ar fi problema rundei 17. În acea problemă se cere determinarea numărului de sume
distincte care se pot obţine folosind nişte obiecte cu valori date folosind fiecare obiect pentru o
sumă cel mult o dată. Din nou o rezolvare directă, folosind marcarea pe un şir de valori logice n-
ar fi obţinut punctaj maxim. Pentru obţinerea punctajului maxim următoarea idee ar fi putut fi
folosită cu succes: şirul de valori logice se condensează într-un şir de întregi reprezentaţi pe 16
biţi, şi acum actualizarea se face asupra a 16 valori deodată. Deci şi aici se câştiga un factor de
viteză egal cu 8 (datorită detaliilor de implementare).
Aplicând acest algoritm numărul de calcule s-a redus la maxn * log maxn, dar nici acest număr
nu este suficient de mic, şi deci
pentru accelerarea algoritmului folosim ideea utilizată în cele două probleme de mai sus.
Împachetăm numărul în întregi, în pachete de câte 30 de biţi, şi atunci deplasarea la stânga (adică
împărţirea la 2) şi scăderea vor fi de 30 de ori mai rapide.
Să încercăm acum rezolvarea unei probleme în care eficienţa este cea mai importantă, mai
importantă decât lizibilitatea codului obţinut. Problema are următorul enunţ...
Se consideră un număr n. Să se determine numerele prime mai mici sau egale cu n, unde n ≤ 10
000 000.
Practic această problemă ne va fi utilă în testarea rapidă a primalităţii numerelor mai mici sau
egale cu n.
Putem încerca mai multe rezolvări, dar în practică cea mai rapidă se dovedeşte de obicei cea
numită ciurul lui Eratostene. Implementarea banală a algoritmului ar fi următoarea:
char at[maxsize]; // vector de valori logice în care numerele prime vor fi marcate cu 0
int n;
int isPrime(int x) {
return (!at[x]) ;
void sieve() {
int i, j;
memset(at,0,sizeof(at)) ;
Observăm că jumătate din timpul folosit de noi la preprocesare este pierdut cu numerele pare.
Marcându-le de la început vom putea ignora numerele pare în preprocesare...
char at[maxsize];
int n;
int isPrime(int x) {
return (!at[x]) ;
void sieve() {
int i, j;
memset(at,0,sizeof(at));
at[i] = 1;
La marcarea multiplilor numărului prim i toate numerele până la i * i au fost deja marcate
deoarece i * i este cel mai mic număr care nu este divizibil cu numere naturale mai mici sau
egale cu i - 1. Deci, avem o a treia versiune a programului...
void sieve() {
int i, j;
memset(at,0,sizeof(at));
at[i] = 1;
at[j] = 1;
Ultima optimizare este optimizarea spaţiului necesar. Într-un tip de date char putem împacheta
opt valori logice şi punând un test suplimentar în metoda isPrime putem elimina şi numerele
pare, astfel vom avea nevoie de un spaţiu de 16 ori mai mic.
int isPrime(int x) {
if (x == 2) return 0 ;
else return 1 ;
else return (at[((x - 3) >> 1) >> 8] & (1 << (((x - 3) >> 1) & 7))) ;
void sieve() {
int i, j;
memset(at, 0, sizeof(at)) ;
COMENTURI DE PE INFOARENA
La algoritmul care numără biții unui număr cred că trebuie să faci un do-while sau să începi
numărătoarea de la 1, pentru că va returna numărul de biți-1. De exemplu, pentru 2, el când face 2&1 =
0, iese și nu mai incrementează rezultatul.
Cod:
Si de ce ai folosi functiile alea de minim / maxim daca sunt mai lente decat cele normale?
Unde e optimizarea?
Cod:
#include <cstdio>
#include <ctime>
#include <cstdlib>
int main()
{
srand(time(0));
start = clock();
end = clock();
start = clock();
s = 0;
end = clock();
Citat din mesajul lui: Mircea Dima din Ianuarie 29, 2010, 17:09:09
alexandru •alexandru92
Citat din mesajul lui: Mircea Dima din Ianuarie 29, 2010, 17:19:14
Cod:
#include <cstdio>
/*
*
*/
using namespace std;
inline unsigned int min( unsigned int x, unsigned int y )
{
if( x < y )
return x;
return y;
}
int main()
{
unsigned int i, j, n=2000000, m=0;
for( i=0, j=n; i <= n; ++i, --j )
m+=min( i, j );
printf( "%u", m );
return 0;
}
Ianuarie 29, 2010, 18:08:53 Mircea Dima •blasterz
Pai am marit n-ul la 200 000 000 si se simte diferenta... metoda normala ia 0.328 sec iar metoda pe biti
0.578.
:-" Ti-am zis ca nu stii sa testezi.
Practic ar merge mai repede sa faci min/max si daca le-ai face prin adunari si scaderi (ex: min(a,b) = (a+b
- abs(a-b) )/2 ) pentru ca if-ul spage intr-un fel secventa de cod... si face niste sarituri la nivel de
asamblare in contrast cu metodele aratate ( pe biti, ce am zis eu etc. ) care se sparg in secvente de
instructiuni succesive fara salturi... asta e si motivul pentru care merge mai repede ce face acolo pe biti...
Cod:
#include <cstdio>
#include <ctime>
#include <cstdlib>
int vec[20001];
int main()
{
srand(time(0));
printf("%d\n", s);
end = clock();
start = clock();
s = 0;
end = clock();
Si la tine e sir constant (practic accesezi aceeasi indici in aceeasi ordine). Si in plus nu spargi cache-ul...de
unde stii ca, compilatorul nu optimizeaza mai bine operatiile pe biti cand nu se sparge cache-ul ?
Uite, eu am facut asa: am declarat un vector int vec[100 000 001]; adica 400 MB. si ii dau min(a[rand() %
nr + 1], a[rand() % nr +1]) . Nu este nici sir constant (ii dau srand(time(0)).
Diferite rulari:
-1368030597
Time minim: 3.600000
-282026249
Time minim normal: 3.570000
163207845
Time minim: 3.570000
-1876227552
Time minim normal: 3.380000
1921398278
Time minim: 3.500000
-1700755686
Time minim normal: 3.800000
605368241
Time minim: 4.180000
842137942
Time minim normal: 4.080000
Cod:
#include <cstdio>
#include <ctime>
#include <cstdlib>
int vec[100000001];
int main()
{
srand(time(0));
start = clock();
for(i = 1; i <= 10000000 ; ++i)
s += min( vec[rand() % nr + 1], vec[rand() % nr + 1]);
printf("%d\n", s);
end = clock();
start = clock();
s = 0;
printf("%d\n", s);
end = clock();
Nu, la mine nu este sir constant pentru ca secventa de operatii nu va fi mereu aceasi cand repornesti
programul pentru ca automat nu sunt aceleasi numere cum erau la tine fiind indici de doua for-uri...
Toata secventa aia era constnta. ( secventa de instructiuni era constanta ). La mine la fiecare pornire de
program era alta si atunci compilatorul nu facea nici o optimizare pentru ca vedea ca nu se poate si ai
vazut diferentele .
In al doilea rand eu nu vb de cache... ci de o chestie mult mai simpla... if-ul este implementat ca o
secventa de "goto" la nivel de asamblare ( nu ma intreba de unde stiu asta... citesti/verifici/te informezi
si vezi si tu daca nu sti asamblare ) si face operatie de salt. Practic cand se translateaza apoi in cod
masina in secventa de 0010101... o sa apara o saritura care face sa mai treaca un timp.
Sincer nu stiu ce sa zic despre datele tale... tind sa cred ca esti carcotasi
( testele sunt facute exact pe sursa malale de mai sus ... daca vrei sa evidentieti diferentele si mai
bine maresc numarul de teste )
-335291704
Time minim: 2.906000
-271732366
Time minim normal: 2.579000
pe linux am incercat sa compilez in 4 moduri distincte, si da acelasi rezultat (adica minim normal mai
mic)
Cel mai mare impact il are cand compilez cu g++ -O2 -lm -static -o x sursa.cpp (diferente mai mari de o
secunda intre ele)
Deci... in alta ordine de idei: (da stiu... nu e bine sa postez consecutiv...dar am obosit sa tot dau
"modifica")
Pe noi ne intereseaza daca una din cele 2 functii scrise in C++ (nu in asm sau altceva) optimizeaza cu
ceva, in special la olimpiade si concursuri...
Raspuns: NU ... nu conteaza pe care le folositi... depinde cum optimizeaza compilatorul, cum se
compileaza (daca se compileaza cu -lm -static, cum e la olimpiade si concursuri, se simte diferenta...in
anumite cazuri).
Pentru programatori in general... conteaza atunci cand se doreste optimizarea la sange... si cred ca e
important sa studiezi care e mai buna din variante... nu cred ca e vreuna din ele strict mai buna.
Eu m-am plictisit de topicul asta...care oricum nu are prea multa importanta.
buna! va scrie un pasionat de informatica din R.Moldova.
M-am apucat de informatica la 27 de ani si acum sunt student (am 29 de ani) la Universitatea Tehnica
din Moldova. Referitor la articolul "operatii pe biti... ciurul lui Eratostene"- care de ltfel e un articol
destul de interesant! Daca nu gresesc asi vrea sa fac unele corectii ale codului, si anume:
#define maxsize 10000000
02.
03.char at[maxsize / 16];
04.int n;
05.
06.int isPrime(int x) {
07.if (!(x & 1))
08.if (x == 2) return 0 ;
09.else return 1 ;
10.else return (at[((x - 3) >> 1) >> 8] & (1 << (((x - 3) >> 1) & 7))) ;
11.}
12.
13.void sieve() {
14.int i, j;
15.memset(at, 0, sizeof(at)) ;
16.for (i = 3; i <= maxsize; i += 2)
17.if (!at[((i - 3) >> 1) >> 8] & (1 << (((i - 3) >> 1) & 7)))
18.for (j = i * i ; j <= maxsize ; j += i + i)
19.at[((i - 3) >> 1) >> 8] |= (1 << (((i - 3) >> 1) & 7))) ;
20.}
in sectiunea 17 in loc de if (!at[((i - 3) >> 1) >> 8] & (1 << (((i - 3) >> 1) & 7)))
ar trebui de scris if (!(at[((i - 3) >> 1) >> 3] & (1 << (((i - 3) >> 1) & 7))))
sau chiar if 17.if (!(at[i >> 4] & (1 << ((i >> 1) & 7))))
astfel evitându-se scăderea repetată (x-3) dar cu prețul pierderii primilor trei biți din primul octet
al char-ului at[], astfel fiind necesar de a rezerva cu un octet mai multă memorie decât este necesar.
A doua observație ar fi ca în secțiunea 19.at[((i - 3) >> 1) >> 8] |= (1 << (((i - 3) >> 1) & 7))) ;
la fel aceiași observație , plus că în loc de i trebuie să fie j, adică:
19.at[((j-3) >> 1) >> 3] |= (1 << (((j - 3) >> 1) & 7))) ;
sau cum am mai spus mai sus :19.if (!(at[j >> 4] & (1 << ((j >> 1) & 7))))
O a treia observație ar fi cea din funcția :
int isPrime(int x) {
07.if (!(x & 1))
08.if (x == 2) return 0 ;
09.else return 1 ;
10.else return (at[((x - 3) >> 1) >> 8] & (1 << (((x - 3) >> 1) & 7))) ;
11.}
și anume :
int isPrime(int x) {
07.if (!(x & 1))
08.if (x == 2) return 1 ;
09.else return 0 ;
10.else return ( ! (at[((x - 3) >> 1) >> 8] & (1 << (((x - 3) >> 1) & 7))) );
11.}
int main()
{
clrscr();
sieve();
printf("\n n= "); scanf("%d", &n);
for (int i=3; i <= n; i+=2)
if (isPrime(i)) printf("%d este prim\n", i);
else printf("%d nu este prim\n", i);
getch();
return 0;
}
int isPrime(int x)
{
if (x <= 1) return 0;
if (!(x & 1))
if (x == 2) return 1;
else return 0;
else return ( !(sita[x >> 4] & (1 << ((x >> 1) & 7))) );
}
void sieve()
{
int i, j;
memset(sita, 0, sizeof(sita));
for (i=3; i * i <= maxsize; i+=2)
if ( !(sita[i >> 4] & (1 << ((i >> 1) & 7))) )
for (j=i * i; j <= maxsize; j+=i + i)
sita[j >> 4] |= (1 << ((j >> 1) & 7));
}