#include <iostream>
#include <fstream>
#include <bits/stdc++.h>
using namespace std;
class graf {
int noduri, muchii;
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) {};
void citireNeorientat(istream & in ) {
for (int i = 0; i < muchii; i++) {
int aux1, aux2; in >> aux1 >> aux2;
adiacenta[aux1].push_back(aux2);
adiacenta[aux2].push_back(aux1);
}
}
void citireOrientat(istream & in ) {
for (int i = 0; i < muchii; i++) {
int aux1, aux2; in >> aux1 >> aux2;
adiacenta[aux1].push_back(aux2);
}
}
vector < int > bfs(int start) {
vector < int > rasp(noduri + 1, -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;
};
}
return rasp;
}
int dfs() {
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;
}
void 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));
}
}
}
}
vector < string > biconex() {
vector < int > timp(noduri + 1, -1), rev(noduri + 1);
stack < pair < int, int >> s;
vector < string > rasp;
int timpo = 0;
// Se se proneste 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;
}
void 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 si
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]);
}
else if (timp[fiu] < timp[tata]) {
rev[tata] = min(rev[tata], timp[fiu]);
}
}
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);
}
}
vector < string > ctc() {
vector < int > timp(noduri + 1, -1), rev(noduri + 1);
unordered_set < int > check;
stack < int > s;
vector < string > rasp;
int timpo = 0;
for (int i = 1; i <= noduri; i++) {
if (timp[i] == -1) {
dfsCtc(i, timp, rev, s, timpo, rasp, check);
}
}
return rasp;
}
void dfsSortaret(int tata, vector < int > & vis, vector < int > & res) {
for (int fiu: adiacenta[tata]) {
if (!vis[fiu]) {
vis[fiu] = 1;
dfsSortaret(fiu, vis, res);
}
}
res.push_back(tata);
}
vector < int > sortaret() {
vector < int > vis(this -> noduri + 1);
vector < int > res;
for (int i = 1; i <= this -> noduri; i++) {
if (vis[i] == 0) {
vis[i] = 1;
dfsSortaret(i, vis, res);
}
}
return res;
}
void dfsCriticalConnections(int tata, vector < int > & timp, vector < int > & rev, int & timpcurent, vector < vector < int >> & rasp, vector < int > & parinte) {
timpcurent++;
timp[tata] = timpcurent;
rev[tata] = timpcurent;
for (int fiu: adiacenta[tata]) {
parinte[fiu] = tata;
if (timp[fiu] == -1) {
dfsCriticalConnections(fiu, timp, rev, timpcurent, rasp, parinte);
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);
}
}
else if (parinte[tata] != fiu) {
rev[tata] = min(rev[tata], timp[fiu]);
}
}
}
vector < vector < int >> criticalConnections() {
vector < int > timp(noduri + 1, -1), rev(noduri + 1), parinte(noduri + 1);
vector < vector < int >> rasp;
int timpo = 0;
for (int i = 1; i <= noduri; i++) {
if (timp[i] == -1) {
dfsCriticalConnections(i, timp, rev, timpo, rasp, parinte);
}
}
return rasp;
}
};
bool havelHakimi(vector < int > & a) {
sort(a.begin(), a.end(), [](int m, int n) {
return m > n;
});
int x = a[0];
if (a.size() == 1 && x >= 1) {
return false;
}
if (x == 0) {
return true;
}
a.erase(a.begin() + 0);
if (x > a.size()) {
return false;
}
for (int i = 0; i < x; i++) {
a[i]--;
if (a[i] < 0) {
return false;
}
}
return havelHakimi(a);
}
void bfsDriver() {
//citire
ifstream in ("bfs.in");
ofstream out("bfs.out");
int n, m, s; in >> n >> m >> s;
graf x(n, m);
x.citireOrientat( in );
//afisare
vector < int > rasp = x.bfs(s);
for (int i = 1; i <= n; i++) {
out << rasp[i] << " ";
}
in.close();
out.close();
}
void dfsDriver() {
//citire
ifstream in ("dfs.in");
ofstream out("dfs.out");
int n, m; in >> n >> m;
graf x(n, m);
x.citireNeorientat( in );
//afisare
out << x.dfs();
in.close();
out.close();
}
void biconexDriver() {
//citire
ifstream in ("biconex.in");
ofstream out("biconex.out");
int n, m; in >> n >> m;
graf x(n, m);
x.citireNeorientat( in );
//afisare
vector < string > fin = x.biconex();
out << fin.size() << '\n';
for (string i: fin) {
out << i;
}
in.close();
out.close();
}
void ctcDriver() {
//citire
ifstream in ("ctc.in");
ofstream out("ctc.out");
int n, m; in >> n >> m;
graf x(n, m);
x.citireOrientat( in );
//afisare
vector < string > fin = x.ctc();
out << fin.size() << '\n';
for (string i: fin) {
out << i;
}
in.close();
out.close();
}
void sortaretDriver() {
//citire
ifstream in ("sortaret.in");
ofstream out("sortaret.out");
int n, m; in >> n >> m;
graf x(n, m);
x.citireOrientat( in );
//afisare
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; in >> n;
vector < int > a;
//afisare
for (int i = 0; i < n; i++) {
int aux; in >> aux;
a.push_back(aux);
}
out << havelHakimi(a);
in.close();
out.close();
}
void criticalConnectionsDriver() {
//citire
int n, m;
cin >> n >> m;
graf x(n, m);
x.citireNeorientat(cin);
//afisare
vector < vector < int >> fin = x.criticalConnections();
for (auto i: fin) {
for (auto j: i) {
cout << j << " ";
}
}
}
int main() {
//Se apeleaza functia corespunzatoare problemei
ctcDriver();
return 0;
}