#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) {
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) {
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
pair < int, int > stop = make_pair(tata, fiu);
unordered_set < int > aux;
string str = "";
aux.insert(stop.first);
aux.insert(stop.second);
str += to_string(stop.first);
str += " ";
str += to_string(stop.second);
str += " ";
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 {
rev[tata] = min(rev[tata], timp[fiu]);
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;
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) {
timpcurent++;
timp[tata] = timpcurent;
rev[tata] = timpcurent;
s.push(tata);
check.insert(tata);
for (int fiu: adiacenta[tata]) {
if (timp[fiu] == -1) {
dfsCtc(fiu, timp, rev, s, timpcurent, rasp, check);
rev[tata] = min(rev[tata], rev[fiu]);
}
else if (check.find(fiu) != check.end()) {
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
biconexDriver();
return 0;
}