#include <iostream>
#include <fstream>
#include <bits/stdc++.h>
using namespace std;
// Clasa pentru graf
class graf {
protected:
// Numarul de noduri si numarul de muchii al grafului
int noduri, muchii;
// Matricea de adiacenta a grafului
vector < vector < int >> adiacenta;
public:
// Constructor cu matrice de adiacenta data
graf(vector < vector < int >> listaAdiacenta, int noduri, int muchii) : adiacenta(listaAdiacenta), noduri(noduri), muchii(muchii) {};
// Constructor fara matrice de adiacenta, se cu initalizeaza una goala
graf(int noduri, int muchii) : adiacenta(noduri + 1), noduri(noduri), muchii(muchii) {};
// Parcurgerea in latime a unui graf. Pornind de la un nod, se parcurg toti vecinii lui si se adauga intr-un queue. Se noteaza timpul la care se ajunge la fiecare.
vector < int > bfs(const int);
// Parcurgea in inaltime a unui graf. Pornind de la un nod, se parcurg toti vecinii lui si se adauga intr-un stack. Se noteaza timpul la care se ajunge la fiecare.
vector < int > dfs(const int);
// Se va face dfs atat timp cat exista un nod care nu a fost accesat de dfs-ul anterior.
int componenteConexe();
};
// Clasa pentru graf neorientat
class grafNeorientat : public graf {
// Functii auxiliare.
void dfsBiconex(int, vector < int >&, vector < int >&, stack < pair < int, int >>&, int&, vector < string >&);
void dfsCriticalConnections(int, vector < int >&, vector < int >&, int&, vector < vector < int >>&, vector < int >&);
int bfsDarb(const int, int&);
public:
// Constructor cu matrice de adiacenta data
grafNeorientat(vector < vector < int >> listaAdiacenta, int noduri, int muchii) : graf(listaAdiacenta, noduri, muchii) {};
// Constructor fara matrice de adiacenta, se cu initalizeaza una goala
grafNeorientat(int noduri, int muchii) : graf(noduri, muchii) {};
// Functie care returneaza componentele biconexe ale unui graf neorientat
vector < string > biconex();
// Functie care returneaza muchiile critice ale unui graf neorientat
vector < vector < int >> criticalConnections();
// Functie care returneaza diametrul unui arbore
int darb();
// Functie care verifica daca un graf neorientat poate fi eulerian
bool isEuler();
// Functie care returneaza ciclul euler al unui graf eulerian
vector<int> euler();
};
// Clasa pentru graf orientat
class grafOrientat : public graf {
// Functii auxiliare
void dfsCtc(int, vector < int >&, vector < int >&, stack < int >&, int&, vector < string >&, unordered_set < int >&);
void dfsSortaret(int, vector < int >&, vector < int >&);
public:
// Constructor cu matrice de adiacenta data
grafOrientat(vector < vector < int >> listaAdiacenta, int noduri, int muchii) : graf(listaAdiacenta, noduri, muchii) {};
// Constructor fara matrice de adiacenta, se cu initalizeaza una goala
grafOrientat(int noduri, int muchii) : graf(noduri, muchii) {};
// Functie care returneaza componentele tare conexe ale unui graf orientat
vector < string > ctc();
// Functie care returneaza o sortare topologica a unui graf orientat
vector < int > sortaret();
};
// Clasa pentru graf neorientat ponderat
class grafNeorientatPonderat : public grafNeorientat {
// Matrice de adianceta care contine si costurile
vector< vector< pair< int, int >>> adiacentaPonderata;
public:
// Constructor cu matrice de adiacenta data
grafNeorientatPonderat(vector < vector < pair<int, int> >> listaAdiacenta, int noduri, int muchii) : adiacentaPonderata(listaAdiacenta), grafNeorientat(noduri, muchii) {};
// Constructor fara matrice de adiacenta, se cu initalizeaza una goala
grafNeorientatPonderat(int noduri, int muchii) : adiacentaPonderata(noduri + 1), grafNeorientat(noduri, muchii) {};
// Functie care creeaza un graf partial de cost minim pornind de la un graf ponderat
vector< vector< int >> apm(vector< vector< int >>&, ostream&);
// Functie care returneaza drumul minim de la nodul 1 la fiecare nod din graf neorientat. Merge si pe costuri negative
unordered_map<int, int> bellmanFord();
};
// Clasa pentru graf orientat ponderat
class grafOrientatPonderat : public grafOrientat {
// Matrice de adianceta care contine si costurile
vector< vector< pair< int, int >>> adiacentaPonderata;
public:
// Constructor cu matrice de adiacenta data
grafOrientatPonderat(vector< vector< pair< int, int >>> listaAdiacenta, int noduri, int muchii) : adiacentaPonderata(listaAdiacenta), grafOrientat(noduri, muchii) {};
// Constructor cu matrice de adiacenta fara costuri data
grafOrientatPonderat(vector<vector<int>> listaAdiacenta, int noduri, int muchii) : grafOrientat(listaAdiacenta, noduri, muchii) {};
// Constructor fara matrice de adiacenta, se cu initalizeaza una goala
grafOrientatPonderat(int noduri, int muchii) : adiacentaPonderata(noduri + 1), grafOrientat(noduri, muchii) {};
// Functie care returneaza drumul minim de la nodul 1 la fiecare nod din graf orientat
unordered_map<int, int> dijkstra();
// Functie care returneaza pornind de la matricea de adiacenta cu costuri cel mai scurt drum de la fiecare nod la fiecare nod
vector<vector<int>> royFloyd(vector<vector<int>>);
// Functie care returneaza ciclul hamiltonian de cost minim al unui graf hamiltonian
int hamilton(vector<vector<int>>);
};
// Clasa pentru retea
class retea : public grafOrientat {
// Matrice de adianceta care contine si costurile
vector< vector< pair< int, int >>> adiacentaPonderata;
vector<vector<int>> cost;
// Functie auxiliara
bool auxBfs(const int start, vector<int>& parinti, vector<vector<int>> matrice, vector<vector<int>> cost);
public:
// Constructor cu matrice de adiacenta data
retea(vector< vector< pair< int, int >>> listaAdiacenta, int noduri, int muchii) : adiacentaPonderata(listaAdiacenta), grafOrientat(noduri, muchii) {};
// Constructor fara matrice de adiacenta, se cu initalizeaza una goala
retea(int noduri, int muchii) : adiacentaPonderata(noduri + 1), grafOrientat(noduri, muchii) {};
// Functie care primeste o retea si returneaza fluxul maxim care poate fi transmis de la sursa la desinatie prin retea
int maxflow();
};
// Clasa pentru graf bipartit
class grafBipartit : public grafNeorientat {
// Numarul de noduri din dreapta, respectiv stanga grafului bipartit
int stanga, dreapta;
// Functii auxiliare
bool bfsCuplaj(vector<int>&, vector<int>&, vector<int>&);
bool dfsCuplaj(int, vector<int>&, vector<int>&, vector<int>&);
public:
// Constructor fara matrice de adiacenta, se cu initalizeaza una goala
grafBipartit(vector<vector<int>> listaAdiacenta, int stanga, int dreapta, int muchii) : stanga(stanga), dreapta(dreapta), grafNeorientat(listaAdiacenta, stanga + dreapta, muchii) {};
// Algoritmul Hopcroft-Karp pentru a gasi cuplajul maxim intr-un graf bipartit
vector<pair<int, int>> cuplaj();
};
// Functii pentru clasa graf
// Parcurgerea in latime a unui graf. Pornind de la un nod, se parcurg toti vecinii lui si se adauga intr-un queue. Se noteaza timpul la care se ajunge la fiecare.
vector < int > graf::bfs(const int start) {
vector < int > rasp(noduri + 1, -1);
vector < int > vis(noduri + 1);
queue < int > q;
// Se incepe de la nodul start
vis[start] = 1;
q.push(start);
rasp[start] = 0;
// Se baga pe rand in queue toate nodurile nevizitate ale nodului din capul queue-ului, si se noteaza timpul la care se ajunge la ele
while (!q.empty()) {
int curr = q.front();
q.pop();
for (int nod : adiacenta[curr])
if (vis[nod] == 0) {
vis[nod] = 1;
q.push(nod);
rasp[nod] = rasp[curr] + 1;
};
}
return rasp;
}
// Parcurgea in inaltime a unui graf. Pornind de la un nod, se parcurg toti vecinii lui si se adauga intr-un stack. Se noteaza timpul la care se ajunge la fiecare.
vector < int > graf::dfs(const int start) {
vector < int > rasp(noduri + 1, -1);
vector < int > vis(noduri + 1);
stack < int > s;
// Se incepe de la nodul start
vis[start] = 1;
s.push(start);
rasp[start] = 0;
// Se baga pe rand in stack toate nodurile nevizitate ale nodului din capul queue-ului, si se noteaza timpul la care se ajunge la ele. Se simuleaza call stack-ul de la recursivitate de dfs
while (!s.empty()) {
int curr = s.top();
s.pop();
for (int nod : adiacenta[curr]) {
if (vis[nod] == 0) {
vis[nod] = 1;
s.push(nod);
rasp[nod] = rasp[curr] + 1;
}
}
}
return rasp;
}
// Se va face dfs atat timp cat exista un nod care nu a fost accesat de dfs-ul anterior.
int graf::componenteConexe() {
vector < int > vis(noduri + 1);
int res = 0;
stack < int > s;
// Se baga pe rand in stack toate nodurile nevizitate ale nodului din capul queue-ului, si se noteaza timpul la care se ajunge la ele. Se simuleaza call stack-ul de la recursivitate de dfs
for (int i = 1; i <= noduri; i++) {
if (vis[i] == 0) {
res++;
vis[i] = 1;
s.push(i);
while (!s.empty()) {
int curr = s.top();
s.pop();
for (int i = adiacenta[curr].size() - 1; i >= 0; i--) {
int nod = adiacenta[curr][i];
if (vis[nod] == 0) {
vis[nod] = 1;
s.push(nod);
}
}
}
}
}
return res;
}
// Functii pentru clasa graf neorientat
// Determina componentele biconexe ale unui graf neorientat, si le pune in vectorul 'rasp'.
void grafNeorientat::dfsBiconex(int tata, vector < int >& timp, vector < int >& rev, stack < pair < int, int >>& s, int& timpCurent, vector < string >& rasp) {
// Tarjan modificat
timpCurent++;
timp[tata] = timpCurent;
rev[tata] = timpCurent;
int fiuRadacina = 0;
// Se tin minte timpii de intoarcere respectiv timpul de intalnire al fiecarui nod in arborele de dfs call
for (int fiu : adiacenta[tata]) {
if (timp[fiu] == -1) {
// Se retine cati fii are fiecare nod in arborele de call dfs
fiuRadacina++;
// Se parcurge prin dfs fiecare nod, si cand se gaseste un nod nevizitat se adauga muchia intr-un stack
s.push(make_pair(tata, fiu));
dfsBiconex(fiu, timp, rev, s, timpCurent, rasp);
// Timpul de revenire al unei muchii este minimul dintre timpul de revenire curent si timpul de revenire al fiului,
// pt ca daca fiul are timp de revenire mai mic si tata il va putea accesa
rev[tata] = min(rev[tata], rev[fiu]);
if ((timp[tata] != 1 && rev[fiu] >= timp[tata]) || (timp[tata] == 1 && fiuRadacina > 1)) {
// Se verifica daca un nod e critic, si sunt 2 cazuri. Primul e cand nodul nu e chiar nodul de incepere al dfs, iar timpul de revenire al fiului este mai mare ca tatal.
// Practic cand nu suntem intr-un ciclu. Al doilea caz este cel discutat la laborator, cand nodul este chiar radacina arborelui, daca are 2 fii atunci e clar nod critic
pair < int, int > stop = make_pair(tata, fiu);
unordered_set < int > aux;
string str = "";
// Se face afisare cu string
aux.insert(stop.first);
aux.insert(stop.second);
str += to_string(stop.first);
str += " ";
str += to_string(stop.second);
str += " ";
// Se parcurge stackul de muchii pana cand dam de muchia care leaga fiul de tata.
// Se face asta pt ca mai intai se va adauga o muchie intre fiu si tata in stack, apoi se va face dfs
// si se vor detecta componentele biconexe ale descendentilor si se vor adauga in stack muchiile descendentilor, iar cad se va iesi din dfs-ul descendentilor unui nod tata
// ce vor ramane in stack vor fi chiar muchiile corespunzatoare componentei biconexe, pana la muchia care leaga tatal de fiu
while (s.top() != stop) {
int nodcur = s.top().first;
if (aux.find(nodcur) == aux.end()) {
str += to_string(nodcur);
str += " ";
aux.insert(s.top().first);
}
s.pop();
}
str += '\n';
rasp.push_back(str);
s.pop();
}
}
else {
// Daca nodul e deja vizitat, atunci am detectat o muchie de intoarcere,
// si timpul de revenire al tatalui devine minimul dintre timpul actual de revenire (ca poate fi mai mic, cu o alta muchie gasita deja)
// si timpul de gasire al fiului
rev[tata] = min(rev[tata], timp[fiu]);
// Verificam sa nu fie o muchie care sa duca in fata (in arbore) de fapt, pt ca deja a fost analizata mai devreme in dfs
if (timp[fiu] < timp[tata]) {
s.push(make_pair(tata, fiu));
}
}
}
}
// Functie care returneaza componentele biconexe ale unui graf neorientat
vector < string > grafNeorientat::biconex() {
vector < int > timp(noduri + 1, -1), rev(noduri + 1);
stack < pair < int, int >> s;
vector < string > rasp;
int timpo = 0;
// Se porneste dfs-ul din toate componentele conexe, si apoi se adauga la raspuns si ce a mai ramas in stack, pt ca si ce ramane va forma o componenta biconexa
for (int i = 0; i < noduri; i++) {
if (timp[i] == -1) {
dfsBiconex(i, timp, rev, s, timpo, rasp);
}
if (!s.empty()) {
unordered_set < int > aux;
string str;
while (!s.empty()) {
if (aux.find(s.top().first) == aux.end()) {
aux.insert(s.top().first);
str += to_string(s.top().first);
str += " ";
}
if (aux.find(s.top().second) == aux.end()) {
aux.insert(s.top().second);
str += to_string(s.top().second);
str += " ";
}
s.pop();
}
str += '\n';
rasp.push_back(str);
}
}
return rasp;
}
// Functie bfs de ajutor pentru diametrul unui arbore
int grafNeorientat::bfsDarb(const int start, int& sosire) {
vector < int > rasp(noduri + 1);
vector < int > vis(noduri + 1);
queue < int > q;
vis[start] = 1;
q.push(start);
rasp[start] = 0;
// Se baga pe rand in queue toate nodurile nevizitate ale nodului din capul queue-ului, si se noteaza timpul la care se ajunge la ele
while (!q.empty()) {
int curr = q.front();
q.pop();
for (int i : adiacenta[curr]) {
if (vis[i] == 0) {
vis[i] = 1;
q.push(i);
rasp[i] = rasp[curr] + 1;
sosire = i;
}
}
}
return rasp[sosire];
}
// Functie care returneaza diametrul unui arbore
int grafNeorientat::darb() {
int last = 0, aux;
aux = bfsDarb(1, last);
aux = bfsDarb(last, last);
return aux + 1;
}
// Functie care verifica daca un graf neorientat poate fi euler
bool grafNeorientat::isEuler() {
// Daca orice nod are gradul impar atunci graful nu poate fi eulerian
for (int i = 1; i < adiacenta.size(); i++) {
if (adiacenta[i].size() % 2 != 0) {
return 0;
}
}
return 1;
}
// Functie care returneaza ciclul euler al unui graf eulerian
vector<int> grafNeorientat::euler() {
// Algoritmul lui Hierholzer
// Se incepe dintr-un nod oarecare si se parcurge graful cam ca intr-un dfs pana ajungem din nou la nodul de inceput, numai ca stergem muchiile prin care trecem
// Acest lucru e garantat sa se intample pt ca graful e eulerien
// Dupa ce am ajuns inapoi, adaugam nodul de start la drumul eulerian final, si ne intoarcem in dfs pentru a gasi un nod care inca are muchii care pot fi accesate
// si incepem la fel ca inainte cu alt nod
// Acest lucru se bazeaza pe faptul ca un graf eulerian se poate descompune in cicluri disjuncte
stack<int> curent;
vector<int> raspuns;
// Se incepe cu un nod oarecare, 1 in cazul nostru
curent.push(1);
// Cat timp exista noduri in ciclul curent
while (!curent.empty()) {
// Se ia primul nod
int nod = curent.top();
// Se verifica daca are vecini
if (adiacenta[nod].size()) {
// Se ia un vecin si se baga in ciclul curent
int nod1 = adiacenta[nod].back();
curent.push(nod1);
// Se sterge muchia
adiacenta[nod].pop_back();
int i;
for (i = 0; i < adiacenta[nod1].size(); i++) {
if (adiacenta[nod1][i] == nod) {
break;
}
}
adiacenta[nod1].erase(adiacenta[nod1].begin() + i);
}
else {
// Daca nodul nu mai are niciun vecin accesibil, de intoarcem la nodul dinaintea lui poate are el
raspuns.push_back(nod);
curent.pop();
}
}
return raspuns;
}
// Functie pentru a gasi muchiile critice dintr-un graf, rezultatul va fi pus in rasp.
void grafNeorientat::dfsCriticalConnections(int tata, vector < int >& timp, vector < int >& rev, int& timpCurent, vector < vector < int >>& rasp, vector < int >& parinte) {
// Tarjan modificat
timpCurent++;
timp[tata] = timpCurent;
rev[tata] = timpCurent;
// Se tin minte timpii de intoarcere respectiv timpul de intalnire al fiecarui nod in arborele de dfs call
for (int fiu : adiacenta[tata]) {
// Tinem minte parintele direct din arborele dfs
parinte[fiu] = tata;
if (timp[fiu] == -1) {
// Cand descoperim un fiu nevizitat facem dfs
dfsCriticalConnections(fiu, timp, rev, timpCurent, rasp, parinte);
// Timpul de revenire al unei muchii este minimul dintre timpul de revenire curent si timpul de revenire al fiului,
// pt ca daca fiul are timp de revenire mai mic si tata il va putea accesa
rev[tata] = min(rev[tata], rev[fiu]);
if (rev[fiu] > timp[tata]) {
vector < int > aux;
aux.push_back(tata);
aux.push_back(fiu);
rasp.push_back(aux);
}
}
// Daca nodul e deja vizitat, atunci am detectat o muchie de intoarcere,
// si timpul de revenire al tatalui devine minimul dintre timpul actual de revenire (ca poate fi mai mic, cu o alta muchie gasita deja)
// si timpul de gasire al fiului
// Se verifica ca muchia sa nu fie una prezenta deja, sa nu fie una care sa mearga in fata in arborele dfs
else if (parinte[tata] != fiu) {
rev[tata] = min(rev[tata], timp[fiu]);
}
}
}
// Functie care returneaza muchiile critice ale unui graf neorientat
vector < vector < int >> grafNeorientat::criticalConnections() {
vector < int > timp(noduri + 1, -1), rev(noduri + 1), parinte(noduri + 1);
vector < vector < int >> rasp;
int timpo = 0;
// Se face dfs pt fiecare componenta conexa
for (int i = 1; i <= noduri; i++) {
if (timp[i] == -1) {
dfsCriticalConnections(i, timp, rev, timpo, rasp, parinte);
}
}
return rasp;
}
// Functii pentru clasa graf orientat
// Determina componentele tare conexe ale unui graf orientat, si le pune in 'rasp'.
void grafOrientat::dfsCtc(int tata, vector < int >& timp, vector < int >& rev, stack < int >& s, int& timpCurent, vector < string >& rasp, unordered_set < int >& check) {
// Tarjan
timpCurent++;
timp[tata] = timpCurent;
rev[tata] = timpCurent;
s.push(tata);
check.insert(tata);
// Se tin minte timpii de intoarcere respectiv timpul de intalnire al fiecarui nod in arborele de dfs call
for (int fiu : adiacenta[tata]) {
if (timp[fiu] == -1) {
// Cand descoperim un fiu nevizitat facem dfs
dfsCtc(fiu, timp, rev, s, timpCurent, rasp, check);
// Timpul de revenire al unei muchii este minimul dintre timpul de revenire curent si timpul de revenire al fiului,
// pt ca daca fiul are timp de revenire mai mic si tata il va putea accesa
rev[tata] = min(rev[tata], rev[fiu]);
}
// Daca nodul e deja vizitat, atunci am detectat o muchie de intoarcere,
// si timpul de revenire al tatalui devine minimul dintre timpul actual de revenire (ca poate fi mai mic, cu o alta muchie gasita deja)
// si timpul de gasire al fiului
else if (check.find(fiu) != check.end()) {
rev[tata] = min(rev[tata], timp[fiu]);
}
}
// Dupa ce se parcurg toti descendentii tatalui, verificam daca timpul de revenire al tatalui este egal cu timpul de intalnire al sau.
// Daca sunt egale, exista doua cazuri, ori tatal face parte dintr-o componenta tare conexa cu descendenti de-ai sai bagati deja in stack,
// ori el este o componenta tare conexa singur.
// Se vor salva nodurile rezultate in stringuri. Check tine minte daca un element e in stack sau nu, caci daca nu e poate forma o componenta tare conexa cu altcineva
if (timp[tata] == rev[tata]) {
string aux = "";
while (s.top() != tata) {
aux += to_string(s.top());
aux += " ";
check.erase(s.top());
s.pop();
}
aux += to_string(s.top());
aux += " ";
check.erase(s.top());
s.pop();
aux += '\n';
rasp.push_back(aux);
}
}
// Functie care returneaza componentele tare conexe ale unui graf orientat.
vector < string > grafOrientat::ctc() {
vector < int > timp(noduri + 1, -1), rev(noduri + 1);
unordered_set < int > check;
stack < int > s;
vector < string > rasp;
int timpo = 0;
// Se porneste dfs-ul din toate componentele conexe, si apoi se adauga la raspuns si ce a mai ramas in stack, pt ca si ce ramane va forma o componenta biconexa
for (int i = 1; i <= noduri; i++) {
if (timp[i] == -1) {
dfsCtc(i, timp, rev, s, timpo, rasp, check);
}
}
return rasp;
}
// Functie pentru a sorta topologim un graf. Ordinea va fi returnata in vectorul 'res'.
void grafOrientat::dfsSortaret(int tata, vector < int >& vis, vector < int >& res) {
// Se face dfs cu recursivitate
for (int fiu : adiacenta[tata]) {
if (!vis[fiu]) {
vis[fiu] = 1;
dfsSortaret(fiu, vis, res);
}
}
// Cand se termina de cautat in fii unui nod, se adauga la raspuns, pt ca inseamna ca nu mai depinde nimeni de el
res.push_back(tata);
}
// Functie care returneaza o sortare topologica a unui graf orientat
vector < int > grafOrientat::sortaret() {
vector < int > vis(this->noduri + 1);
vector < int > res;
// Se face dfs pt fiecare componenta conexa
for (int i = 1; i <= this->noduri; i++) {
if (vis[i] == 0) {
vis[i] = 1;
dfsSortaret(i, vis, res);
}
}
return res;
}
// Functii pentru clasa graf neorientat ponderat
// Functie care creeaza un graf partial de cost minim pornind de la un graf ponderat
vector< vector< int >> grafNeorientatPonderat::apm(vector< vector< int >>& much, ostream& out) {
// Kruskal
// Se vor adauga muchii in ordine crescatoare care sa nu inchida un ciclu in graf
// Vom realiza asta retinand tatal fiecarui nod din arborii partiali formati pana la un moment dat. Astea vor fi puse in vectorul tata
vector< int > tata(noduri + 1);
vector< vector < int >> rasp;
// Sortam muchiile dupa cost
sort(much.begin(), much.end(), [](vector<int> a, vector<int> b) {
return a[2] < b[2];
});
int i = 0, j = 0;
long long s = 0;
// Initializam vectorul tata, fiecarui nod ii atribuim tata chiar pe el, pentru a ne da seama cand nodul nu are de fapt tata
for (int i = 1; i < tata.size(); i++) {
tata[i] = i;
}
// Ciclam prin muchii pana cand am adaugat suficiente ca graful sa fie conex
while (i < this->noduri - 1) {
// Se iau nodurile care formeaza o muchie
int nod1 = much[j][0], nod2 = much[j][1];
int tata1 = nod1, tata2 = nod2;
// Se gaseste radacina arborilor partiali din care fac parte nodurile
while (tata1 != tata[tata1]) {
tata1 = tata[tata1];
}
while (tata2 != tata[tata2]) {
tata2 = tata[tata2];
}
// Daca 2 noduri fac parte din arbori diferiti, adica radacinile lor sunt diferite, am gasit o muchie pe care s-o adaugam in arborele partial de cost minim
if (tata1 != tata2) {
// Adaugam costul muchiei adaugate la costul final
s += much[j][2];
// Unim 2 arbori partiali
// Unim mereu arborele cu radacina mai mare dpdv lexicografic cu arborele cu radacina mai mica, pentru a-i avea intr-o anumita ordine si pentru a fi sanse mai mari sa se lege arbori
// Direct de radacina, si deci sa avem inaltimi mai mici ale arborilor
if (tata[tata1] < tata[tata2]) {
tata[tata1] = tata[tata2];
}
else {
tata[tata2] = tata[tata1];
}
// Adaugam muchia la raspuns
rasp.push_back({ nod1, nod2 });
i++;
}
j++;
}
// Afisare suma si numar muchii din graf
out << s << '\n' << this->noduri - 1 << '\n';
return rasp;
}
// Functie care returneaza drumul minim de la nodul 1 la fiecare nod din graf neorientat. Merge si pe costuri negative
unordered_map<int, int> grafNeorientatPonderat::bellmanFord() {
// Map sa tinem minte costul pana la fiecare mod
unordered_map<int, int> m;
// Queue pentru a tine minte nodurile care ar putea fi relaxate
queue<int> q;
// De cate ori a fost relaxat un nod
vector<int> cnt(noduri + 1);
// Daca un nod este in queue sau nu
unordered_set<int> check;
// Incepem de la nodul 1
m[1] = 0;
q.push(1);
check.insert(1);
// Cat timp coada are noduri in ea, parcurgem nodurile din coada
while (!q.empty()) {
// Luam nodul din capul cozii
int nod1 = q.front();
check.erase(nod1);
q.pop();
// Parcurgem lista de adiacenta a nodului pe care l-am luat
for (pair<int, int> muchie : adiacentaPonderata[nod1]) {
// Retinem nodurile si costurile la care duc muchiile din nod
int nod2 = muchie.first, cost = muchie.second;
// Daca un nod nu are cost sau costul pe care il avem deja e mai mare decat costul pe care l-am forma cu noua muchie gasita, facem update
if (m.find(nod2) == m.end() || m[nod2] > m[nod1] + cost) {
// Updatam costul
m[nod2] = m[nod1] + cost;
// Daca al doilea nod nu este in queue, il bagam, pt ca stim ca exista posibiltatea ca vecinii lui sa poata fi relaxati
if (check.find(nod2) == check.end()) {
q.push(nod2);
check.insert(nod2);
cnt[nod2]++;
// Daca un nod a fost relaxat de mai multe ori decat sunt noduri, e clar ca exista un ciclu care reduce mereu costul
if (cnt[nod2] > noduri) {
// Returnam un map gol pentru a semnala acest lucru
m.clear();
return m;
}
}
}
}
}
return m;
}
// Functii pentru clasa graf orientat ponderat
// Functie care returneaza drumul minim de la nodul 1 la fiecare nod din graf orientat
unordered_map<int, int> grafOrientatPonderat::dijkstra() {
// Aici tinem distantele
unordered_map<int, int> m;
// Priority queue (heap) pentru a tine minte mereu nodul la care se poate ajunge cel mai rapid
priority_queue<vector<int>> p;
// Tinem minte daca nodul a fost deja vizitat
vector<bool> vis(noduri + 1, false);
// Stim ca incepem din nodul 1 mereu deci distanta va fi 0, si il adaugam in heap
m[1] = 0;
p.push({ 0, 1 });
// Mergem prin priority queue pana cand nu mai avem ce lua din el
while (!p.empty()) {
// Luam cel mai mic nod
int curNod = p.top()[1];
p.pop();
// Daca nodul nu a fost deja analizat, incepem analizarea lui
if (!vis[curNod]) {
vis[curNod] = true;
// Ne uitam la vecinii lui
for (auto i : adiacentaPonderata[curNod]) {
int nod = i.first, cost = i.second;
// Daca distanta pana la nod nu a fost inca determinata sau e mai mare decat lungimea calculata acum, o modificam
if (m.find(nod) == m.end() || m[nod] > m[curNod] + cost) {
m[nod] = m[curNod] + cost;
p.push({ -(m[nod]), nod });
}
}
}
}
return m;
}
// Functie care returneaza pornind de la matricea de adiacenta cu costuri cel mai scurt drum de la fiecare nod la fiecare nod
vector<vector<int>> grafOrientatPonderat::royFloyd(vector<vector<int>> rasp) {
// Incearca sa gaseasca pentru fiecare 2 noduri i si j, un nod k prin care daca trecem sa devina costul de la i la j mai mic
for (int k = 1; k <= noduri; k++) {
for (int i = 1; i <= noduri; i++) {
for (int j = 1; j <= noduri; j++) {
// Verificam conditia enuntata mai sus. Avem grija ca i si j sa fie accesibile din k iar i si j sa nu fie chiar acelasi nod
if ((rasp[i][k] + rasp[k][j] < rasp[i][j] || rasp[i][j] == 0) && rasp[k][j] && rasp[i][k] && i != j) {
rasp[i][j] = rasp[i][k] + rasp[k][j];
}
}
}
}
return rasp;
}
// Functie care returneaza ciclul hamiltonian de cost minim al unui graf hamiltonian
int grafOrientatPonderat::hamilton(vector<vector<int>> cost)
{
#define hamiltonHelper 100000000
const int constrangere1 = 300000;
const int constrangere2 = 20;
int raspuns = hamiltonHelper;
vector<vector<int>> matrice(constrangere1, vector<int>(constrangere2));
for (int i = 0; i < 1 << noduri; i++) {
for (int j = 0; j < noduri; j++) {
matrice[i][j] = hamiltonHelper;
}
}
matrice[1][0] = 0;
for (int i = 0; i < 1 << noduri; i++) {
for (int j = 0; j < noduri; j++) {
if (i & (1 << j)) {
for (int nod : adiacenta[j]) {
if (i & (1 << nod)) {
matrice[i][j] = min(matrice[i][j], matrice[i ^ (1 << j)][nod] + cost[nod][j]);
}
}
}
}
}
for (int nod : adiacenta[0]) {
raspuns = min(raspuns, matrice[(1 << noduri) - 1][nod] + cost[nod][0]);
}
return raspuns;
}
// Functii pentru clasa retea
// Functie ajutatoare bfs pentru maxflow
bool retea::auxBfs(const int start, vector<int>& parinti, vector<vector<int>> matrice, vector<vector<int>> cost) {
vector < int > vis(noduri + 1);
queue < int > q;
q.push(start);
vis[1] = 1;
// Se baga pe rand in queue toate nodurile nevizitate ale nodului din capul queue-ului, si se noteaza timpul la care se ajunge la ele
while (!q.empty()) {
int curr = q.front();
q.pop();
for (int i : adiacenta[curr]) {
if (!vis[i] && cost[curr][i] > matrice[curr][i]) {
parinti[i] = curr;
q.push(i);
vis[i] = 1;
}
}
}
return vis[noduri];
}
// Functie care primeste o retea si returneaza fluxul maxim care poate fi transmis de la sursa la desinatie prin retea
int retea::maxflow() {
vector<int> parinti(noduri + 1);
vector<vector<int>> matrice(noduri + 1, vector<int>(noduri + 1));
int m = 0, start = 1, sosire = noduri, currm = INT_MAX;
while (this->auxBfs(start, parinti, matrice, cost)) {
for (auto p : adiacenta[noduri]) {
if (cost[p][noduri] > matrice[p][noduri]) {
parinti[noduri] = p;
currm = cost[p][sosire] - matrice[p][sosire];
int j;
for (int i = p; i != 1; i = parinti[i]) {
j = parinti[i];
currm = min(currm, cost[j][i] - matrice[j][i]);
}
matrice[p][sosire] += currm;
matrice[sosire][p] -= currm;
for (int i = p; i != 1; i = parinti[i]) {
j = parinti[i];
matrice[j][i] += currm;
matrice[i][j] -= currm;
}
m += currm;
}
}
}
return m;
}
// Functii pentru clasa graf bipartit
// Dfs modificat pentru algoritmul de cuplaj. Returneaza true daca exista un drum de la un nod dat la un nod care poate fi inclus in cuplaj.
bool grafBipartit::dfsCuplaj(int start, vector<int>& perecheStanga, vector<int>& perecheDreapta, vector<int>& distanta) {
if (!start) {
return true;
}
for (int nod : adiacenta[start]) {
// Daca nodurile sunt consecutive in bfs, mergem mai departe in dfs
if (distanta[perecheDreapta[nod]] == distanta[start] + 1) {
if (dfsCuplaj(perecheDreapta[nod], perecheStanga, perecheDreapta, distanta)) {
// Daca s-a gasit un drum bun, atunci s-a gasit o pereche de noduri
perecheDreapta[nod] = start;
perecheStanga[start] = nod;
return true;
}
}
}
// Daca nu s-a gasit un drum se ajunge aici si atunci distanta nodului de start va fi infinit pentru ca nu exista drum bun pornind de la el
distanta[start] = INT_MAX;
return false;
}
// Bfs modificat pentru algoritmul de cuplaj. Returneaza true daca exista un drum oarecare la un nod care poate fi inclus in cuplaj.
bool grafBipartit::bfsCuplaj(vector<int>& perecheStanga, vector<int>& perecheDreapta, vector<int>& distanta) {
queue<int> q;
// Initial bagam toate nodurile libere in queue ca sa fie procesate de bfs
for (int nod = 1; nod <= stanga; nod++) {
if (perecheStanga[nod]) {
distanta[nod] = INT_MAX;
}
else {
q.push(nod);
distanta[nod] = 0;
}
}
distanta[0] = INT_MAX;
// Se face bfs
while (!q.empty()) {
int nod1 = q.front();
q.pop();
// Daca un nod poate micsora distanta drumului ii parcurgem vecinii
if (distanta[nod1] < distanta[0]) {
for (int nod2 : adiacenta[nod1]) {
// Daca pereche nodului vecin nu a fost considerat inca il bagam pe drum
if (distanta[perecheDreapta[nod2]] == INT_MAX) {
distanta[perecheDreapta[nod2]] = distanta[nod1] + 1;
q.push(perecheDreapta[nod2]);
}
}
}
}
// Se returneaza daca exista vreun drum, adica daca s-a schimbat timpul drumului
return (distanta[0] != INT_MAX);
}
// Algoritmul Hopcroft-Karp pentru a gasi cuplajul maxim intr-un graf bipartit// Algoritmul Hopcroft-Karp pentru a gasi cuplajul maxim intr-un graf bipartit
vector<pair<int, int>> grafBipartit::cuplaj()
{
// Se cauta un drum care alterneaza muchii care pot fi folosite la cuplaj si muchii care nu. Drumul incepe si se termina in noduri care pot fi adaugate la cuplaj
// Se tin minte perechile nodurilor din dreapta si stanga grafului, si distanta de la nodurile din stanga pe drumuri
vector<int> perecheStanga(stanga + 1);
vector<int> perecheDreapta(dreapta + 1);
vector<int> distanta(stanga + 1);
// Cat timp exista un drum care poate fi gasit
while (bfsCuplaj(perecheStanga, perecheDreapta, distanta)) {
// Gasim un nod liber
for (int i = 1; i <= stanga; i++) {
if (!perecheStanga[i]) {
// Cautam drum pornind de la el
dfsCuplaj(i, perecheStanga, perecheDreapta, distanta);
}
}
}
// Se construieste raspunsul
vector<pair<int, int>> raspuns;
for (int i = 1; i <= stanga; i++) {
if (perecheStanga[i]) {
raspuns.push_back(make_pair(i, perecheStanga[i]));
}
}
return raspuns;
}
// Functii care nu se apeleaza pe un graf
// Functie care primeste un vector care contine gradele nodurilor unui graf, si determina daca se poate forma un graf cu ele sau nu. Este in afara clasei pentru ca nu face operatii pe un graf
bool havelHakimi(vector < int >& a) {
// Mai intai se sorteaza vectorul ca sa avem mereu nodul cu cel mai mare grad primul
sort(a.begin(), a.end(), [](int m, int n) {
return m > n;
});
int x = a[0];
// Daca mai e un singur nod in vector cu grad nenul, evident, nu se va putea face graf cu el
if (a.size() == 1 && x >= 1) {
return false;
}
// Daca primul nod e 0, tinand cont ca vectorul e sortat crescator, atunci toate sunt 0 si e bine, se poate forma graf
if (x == 0) {
return true;
}
// Se sterge primul element din vector, ii facem legaturile
a.erase(a.begin() + 0);
// Daca nodul are grad mai mare decat sunt legaturi posibile atunci e clar ca nu e posibil sa facem graf
if (x > a.size()) {
return false;
}
// 'Conectam' primul nod la urmatoarele noduri din vector, cate ne spune gradul lui
for (int i = 0; i < x; i++) {
a[i]--;
// Daca un nod ajunge cu gradul pe minus, e clar ca nu se paote face graf
if (a[i] < 0) {
return false;
}
}
// Apelam recursiv cu noul vector format
return havelHakimi(a);
}
// Functie care poate face 2 actiuni, uneste 2 paduri sau afiseaza daca 2 noduri sunt in aceeasi padure sau nu
void disjoint(istream& in, ostream& out) {
int n, m;
in >> n >> m;
// Vectorul in care tinem minte tatii, initial tatal lui i va fi chiar i, pt totul e deconectat
vector< int > tata(n + 1);
for (int i = 1; i < tata.size(); i++) {
tata[i] = i;
}
// Mergem prin operatii
for (int i = 0; i < m; i++) {
int tip, nod1, nod2;
in >> tip >> nod1 >> nod2;
int tata1 = nod1, tata2 = nod2;
// Descoperim care sunt radacinile arborilor nodurilor de care avem nevoie
while (tata[tata1] != tata1) {
tata1 = tata[tata1];
}
while (tata[tata2] != tata2) {
tata2 = tata[tata2];
}
// Vedem de ce tip e operatia
if (tip == 1) {
// Se unesc 2 arbori
tata[tata1] = tata[tata2];
}
else {
// Daca radacinile celor 2 arbori corespunzatori nodurilor date coincid, e clar ca si arborii coincid
if (tata1 == tata2) {
out << "DA\n";
}
else {
out << "NU\n";
}
// Dupa ce facem actiunea asta, se face o euristica, si anume unim toate nodurile din arborii tocmai accesati chiar de radacina arborilor in care sunt
while (tata[nod1] != nod1) {
tata[nod1] = tata1;
nod1 = tata[nod1];
}
while (tata[nod2] != nod2) {
tata[nod2] = tata2;
nod2 = tata[nod2];
}
}
}
}
// Functii in care se face citirea si afisarea specifica fiecarei probleme
void bfsDriver() {
// https://infoarena.ro/problema/bfs
// Citire
ifstream in("bfs.in");
ofstream out("bfs.out");
int n, m, s;
in >> n >> m >> s;
vector<vector<int>> adiacenta(n + 1);
for (int i = 0; i < m; i++) {
int aux1, aux2;
in >> aux1 >> aux2;
adiacenta[aux1].push_back(aux2);
}
grafOrientat x(adiacenta, n, m);
// Afisare
vector < int > rasp = x.dfs(s);
for (int i = 1; i <= n; i++) {
out << rasp[i] << " ";
}
in.close();
out.close();
}
void componenteConexeDriver() {
// https://infoarena.ro/problema/dfs
// Citire
ifstream in("dfs.in");
ofstream out("dfs.out");
int n, m;
in >> n >> m;
vector<vector<int>> adiacenta(n + 1);
for (int i = 0; i < m; i++) {
int aux1, aux2;
in >> aux1 >> aux2;
adiacenta[aux1].push_back(aux2);
adiacenta[aux2].push_back(aux1);
}
grafNeorientat x(adiacenta, n, m);
// Afisare
out << x.componenteConexe();
in.close();
out.close();
}
void biconexDriver() {
// https://infoarena.ro/problema/biconex
// Citire
ifstream in("biconex.in");
ofstream out("biconex.out");
int n, m;
in >> n >> m;
vector<vector<int>> adiacenta(n + 1);
for (int i = 0; i < m; i++) {
int aux1, aux2;
in >> aux1 >> aux2;
adiacenta[aux1].push_back(aux2);
adiacenta[aux2].push_back(aux1);
}
grafNeorientat x(adiacenta, n, m);
// Afisare
vector < string > fin = x.biconex();
out << fin.size() << '\n';
for (string i : fin) {
out << i;
}
in.close();
out.close();
}
void ctcDriver() {
// https://infoarena.ro/problema/ctc
// Citire
ifstream in("ctc.in");
ofstream out("ctc.out");
int n, m;
in >> n >> m;
vector<vector<int>> adiacenta(n + 1);
for (int i = 0; i < m; i++) {
int aux1, aux2;
in >> aux1 >> aux2;
adiacenta[aux1].push_back(aux2);
}
grafOrientat x(adiacenta, n, m);
// Afisare
vector < string > fin = x.ctc();
out << fin.size() << '\n';
for (string i : fin) {
out << i;
}
in.close();
out.close();
}
void sortaretDriver() {
// https://infoarena.ro/problema/sortaret
// Citire
ifstream in("sortaret.in");
ofstream out("sortaret.out");
int n, m;
in >> n >> m;
vector<vector<int>> adiacenta(n + 1);
for (int i = 0; i < m; i++) {
int aux1, aux2;
in >> aux1 >> aux2;
adiacenta[aux1].push_back(aux2);
}
grafOrientat x(adiacenta, n, m);
// Afisare
// Citim vectorul invers fata de cum l-am creat, pt ca ultimul nod care e accesat e nodul cu cele mai multe dependente
vector < int > res = x.sortaret();
for (int i = res.size() - 1; i >= 0; i--) {
out << res[i] << " ";
}
in.close();
out.close();
}
void havelHakimiDriver() {
// Citire
ifstream in("havelhakimi.in");
ofstream out("havelhakimi.out");
int n, k = 1;
in >> n;
vector < int > a;
for (int i = 0; i < n; i++) {
int aux;
in >> aux;
a.push_back(aux);
}
// Afisare
out << havelHakimi(a);
in.close();
out.close();
}
void criticalConnectionsDriver() {
// https://leetcode.com/problems/critical-connections-in-a-network/
// Citire
int n, m;
cin >> n >> m;
vector<vector<int>> adiacenta(n + 1);
for (int i = 0; i < m; i++) {
int aux1, aux2;
cin >> aux1 >> aux2;
adiacenta[aux1].push_back(aux2);
adiacenta[aux2].push_back(aux1);
}
grafNeorientat x(adiacenta, n, m);
// Afisare
vector < vector < int >> fin = x.criticalConnections();
for (auto i : fin) {
for (auto j : i) {
cout << j << " ";
}
}
}
void apmDriver() {
// https://infoarena.ro/problema/apm
// Citire
ifstream in("apm.in");
ofstream out("apm.out");
int n, m;
in >> n >> m;
grafNeorientatPonderat x(n, m);
vector< vector< int >> much;
for (int i = 0; i < m; i++) {
int aux1, aux2, aux3;
in >> aux1 >> aux2 >> aux3;
much.push_back({ aux1, aux2, aux3 });
}
// Afisare
vector< vector< int >> v = x.apm(much, out);
for (auto i : v) {
out << i[0] << " " << i[1] << "\n";
}
in.close();
out.close();
}
void disjointDriver() {
// // https://infoarena.ro/problema/disjoint
// Citire
ifstream in("disjoint.in");
ofstream out("disjoint.out");
// Apelare
disjoint(in, out);
in.close();
out.close();
}
void dijkstraDriver() {
// https://infoarena.ro/problema/dijkstra
// Citire
ifstream in("dijkstra.in");
ofstream out("dijkstra.out");
int n, m;
in >> n >> m;
vector< vector< pair< int, int >>> adiacenta(n + 1);
for (int i = 0; i < m; i++) {
int aux1, aux2, aux3;
in >> aux1 >> aux2 >> aux3;
adiacenta[aux1].push_back(make_pair(aux2, aux3));
}
grafOrientatPonderat x(adiacenta, n, m);
// Afisare
unordered_map<int, int> v = x.dijkstra();
for (int i = 2; i <= n; i++) {
if (v.find(i) == v.end()) {
out << "0 ";
}
else {
out << v[i] << " ";
}
}
in.close();
out.close();
}
void bellmanfordDriver() {
// https://infoarena.ro/problema/bellmanFord
// Citire
ifstream in("bellmanFord.in");
ofstream out("bellmanFord.out");
int n, m;
in >> n >> m;
vector<vector<pair<int, int>>> muchii(n + 1);
for (int i = 0; i < m; i++) {
int aux1, aux2, aux3;
in >> aux1 >> aux2 >> aux3;
muchii[aux1].push_back(make_pair(aux2, aux3));
}
grafNeorientatPonderat x(muchii, n, m);
// Afisare
unordered_map<int, int> v = x.bellmanFord();
if (v.empty()) {
out << "Ciclu negativ!";
}
else {
for (int i = 2; i <= n; i++) {
out << v[i] << " ";
}
}
in.close();
out.close();
}
void maxflowDriver() {
// https://infoarena.ro/problema/maxflow
// Citire
ifstream in("maxflow.in");
ofstream out("maxflow.out");
int n, m;
in >> n >> m;
vector< vector< pair< int, int >>> adiacenta(n + 1);
for (int i = 0; i < m; i++) {
int aux1, aux2, aux3;
in >> aux1 >> aux2 >> aux3;
adiacenta[aux1].push_back(make_pair(aux2, aux3));
}
retea x(adiacenta, n, m);
// Afisare
out << x.maxflow();
in.close();
out.close();
}
void royfloydDriver() {
// https://infoarena.ro/problema/royFloyd
// Citire
ifstream in("royfloyd.in");
ofstream out("royfloyd.out");
int n, m = 0;
in >> n;
vector<vector<int>> matrice(n + 1, vector<int>(n + 1));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
in >> matrice[i][j];
}
}
grafOrientatPonderat x(n, m);
// Afisare
vector<vector<int>> rasp = x.royFloyd(matrice);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
out << rasp[i][j] << " ";
}
out << '\n';
}
in.close();
out.close();
}
void darbDriver() {
// https://infoarena.ro/problema/darb
// Citire
ifstream in("darb.in");
ofstream out("darb.out");
int n, m = 0, a, b;
in >> n;
vector < vector < int >> listaAdiacenta(n + 1);
while (in >> a >> b) {
listaAdiacenta[a].push_back(b);
listaAdiacenta[b].push_back(a);
}
grafNeorientat x(listaAdiacenta, n, m);
// Afisare
out << x.darb();
in.close();
out.close();
}
void eulerDriver() {
// https://infoarena.ro/problema/ciclueuler
// Citire
ifstream in("ciclueuler.in");
ofstream out("ciclueuler.out");
int n, m;
in >> n >> m;
vector<vector<int>> adiacenta(n + 1);
for (int i = 0; i < m; i++) {
int aux1, aux2;
in >> aux1 >> aux2;
adiacenta[aux1].push_back(aux2);
adiacenta[aux2].push_back(aux1);
}
grafNeorientat x(adiacenta, n, m);
if (!x.isEuler()) {
out << -1;
return;
}
// Afisare
vector<int> raspuns = x.euler();
for (int i = 0; i < raspuns.size() - 1; i++) {
out << raspuns[i] << " ";
}
in.close();
out.close();
}
void hamiltonDriver() {
// https://infoarena.ro/problema/hamilton
// Citire
ifstream in("hamilton.in");
ofstream out("hamilton.out");
int n, m;
in >> n >> m;
vector<vector<int>> adiacenta(n + 1);
vector<vector<int>> cost(n + 1, vector<int>(n + 1, INT_MAX));
for (int i = 1; i <= m; i++) {
int aux1, aux2, aux3;
in >> aux1 >> aux2 >> aux3;
adiacenta[aux2].push_back(aux1);
cost[aux1][aux2] = aux3;
}
grafOrientatPonderat x(adiacenta, n, m);
// Afisare
int raspuns = x.hamilton(cost);
if (raspuns == hamiltonHelper) {
out << "Nu exista solutie";
}
else {
out << raspuns;
}
in.close();
out.close();
}
void cuplajDriver() {
// https://infoarena.ro/problema/cuplaj
// Citire
ifstream in("cuplaj.in");
ofstream out("cuplaj.out");
int l, r, m;
in >> l >> r >> m;
vector<vector<int>> adiacenta(l + r + 1);
for (int i = 0; i < m; i++) {
int aux1, aux2;
in >> aux1 >> aux2;
adiacenta[aux1].push_back(aux2);
}
grafBipartit x(adiacenta, l, r, m);
// Afisare
vector<pair<int, int>> v = x.cuplaj();
out << v.size() << '\n';
for (auto i : v) {
out << i.first << ' ' << i.second << '\n';
}
in.close();
out.close();
}
int main() {
// Se apeleaza functia corespunzatoare problemei
componenteConexeDriver();
return 0;
}