Pagini recente » Cod sursa (job #2278034) | Cod sursa (job #165849) | Cod sursa (job #756783) | Cod sursa (job #3280861) | Cod sursa (job #2747044)
#include <iostream>
#include <fstream>
#include <vector>
using namespace std;
ifstream fin("hashuri.in");
ofstream fout("hashuri.out");
#define MOD 666013
int n;
vector<int> G[MOD];
inline vector<int>::iterator find_value(int x)
{
int list = x % MOD;
vector<int>::iterator it;
for (it = G[list].begin(); it != G[list].end(); ++it)
if (*it == x)
return it;
return G[list].end();
}
inline void insert_value(int x)
{
int list = x % MOD;
if (find_value(x) == G[list].end())
G[list].push_back(x);
}
inline void erase_value(int x)
{
int list = x % MOD;
vector<int>::iterator it = find_value(x);
if (it != G[list].end())
G[list].erase(it);
}
int main()
{
int op, x;
fin>>n;
for (int i=0; i<n; i++)
{
fin>>op>>x;
if (op == 1) // inserare
{
insert_value(x);
continue;
}
if (op == 2) // stergere
{
erase_value(x);
continue;
}
if (find_value(x) != G[x % MOD].end())
fout << 1 <<'\n';
else fout << 0 <<'\n';
}
return 0;
}
/*
O abordare directa a problemei are complexitatea O(N2) si trebuie sa obtina 30 de puncte.
Problema poate fi rezolvata si in complexitate O(NlogN) folosind arbori echilibrati de cautare, precum AVL-uri, arbori rosu-negru sau treapuri.
Aceste structuri de date au dezavantajul ca sunt dificil de implementat, fapt care le face inabordabile in timp de concurs. Programatorii C++ pot totusi
evita implementarea manuala a acestor structuri, putand folosi ca alternativa container-ul set din STL. Acest container simuleaza comportamentul unui arbore echilibrat, efectuand toate cele trei operatii mentionate in enunt in timp logaritmic. O sursa pe aceasta idee se gaseste aici si obtine 70 de puncte.
Pentru a rezolva eficient in practica problema de fata putem folosi tabele hash (cunoscute in literatura de specialitate si ca tabele de dispersie).
Un articol referitor la acelasi subiect se gaseste si pe wikipedia. Desi teoretic complexitatea acestei structuri de date ramane O(N) pe cazul cel mai
defavorabil, in practica operatiile uzuale precum cautarea, adaugarea sau stergerea se realizeaza foarte rapid. Ideea din spatele hashurilor este de a
asocia fiecarui element dintr-o multime data o valoare unica, obtinuta printr-o functie de hash H. Valorile functiei H sunt de obicei numere naturale.
Elementele multimii (care pot fi numere naturale, siruri de caractere, matrici, etc.) se vor numi chei. Avantajul asocierilor de tipul cheie-valoare
este dat de usurinta cu care putem manipula valorile, in special in cazul in care acestea au dimensiuni mici. Astfel, vom retine o a doua multime (pe
care o vom nota cu M), care sa contina toate valorile de forma H(i), unde i apartine multimii asupra careia se efectueaza operatiile. Atunci cand
adaugam un element i la prima multime trebuie de fapt sa adaugam H(i) la multimea M, iar cand stergem un element i sa stergem H(i). Interogarea unui
element i se reduce la a afla daca elementul H(i) se gaseste in M. Aceasta abordare functioneaza cand functia H este injectiva, adica din oricare doua
chei distincte se obtin valori distincte. Cel mai simplu exemplu este sa alegem H(x) = x si sa retinem multimea M ca un vector boolean, in care pe
pozitia i apare true daca si numai daca i este in multime.
*/
/// Totusi, pentru aceasta problema nu putem construi o functie H injectiva, deoarece in acest caz codomeniul ar contine cel putin 2 miliarde de
/// elemente iar memoria este limitata la 16 MB. In acest caz putem considera o functie H neinjectiva, insa trebuie sa tratam aparitia coliziunilor,
/// adica acele cazuri in care doua chei distincte furnizeaza aceeasi valoare. Fie functia de hash H(x) = x modulo P, unde P este un numar natural,
/// ales de obicei ca fiind un numar prim. Vom retine P liste simplu inlantuite, indexate de la 0 la P-1, unde lista i va retine toate acele chei x
/// din multime care au H(x) = i, sau, altfel spus, toate numerele din multime care dau restul i la impartirea cu P. Atunci cand efectuam o operatie
/// de tipul 1, vom adauga elementul x la lista corespunzatoare din cele P (si anume la lista H(x)), iar pentru o operatie de tipul 2 vom sterge
/// elementul x din lista H(x). Operatia 3 se trateaza similar. Se observa ca daca P este comparabil cu N, distributia cheilor se face in practica
/// cat mai uniform, adica cele P liste au lungime mica iar toate cele trei operatii executa putine calcule, programul devenind deci foarte rapid.
/*
Programatorii C++ mai au la indemana in STL container-ul map, care creeaza asocieri intre elemente ce pot fi si de tipuri diferite. Din pacate
operatiile efectuate de acest container au tot complexitate O(logN), dar este foarte usor de manevrat.
*/