// CTC - TARJAN
// Created by Radu Buzas on 22.10.2022.
//
/*
* Complexitate timp O(n+m)
*
* Algoritmul implmentat este algoritmul lui Tarjan pentru componente tari conexe. Asignez fiecarui nod parcurs prin DFS un id si un low.
* ID-ul simbolizeaza de fapt la al catelea nod sunt, iar low este id-ul minim al unui nod din subgraful tare conex. Low este actualizat
* la fiecare pas al DFS-ului.
*
* Pornesc de la un nod aleator, verific vecinii, daca sunt nevizitati ii vizitez si ulterior verific daca label-ul(low) lor este mai mic
* decat cel al nodului curent, practic fac un minim dintre low[k], unde k este nodul curent, si low[i], unde i poate fi orice vecin al
* nodului curent.
* Cand vizitez un nod il introduc pe stiva.
* Daca nodul vecin al nodul curent este deja vizitat SI este pe stiva, atunci calculez minimul dintre low[k] si low[i], stocand rezultatul
* in low[k].
* Dupa verificarea vecinilor verific daca label-ul(low) corespunde cu id, daca corespund atunci scot de pe stiva elementele pana dau de
* nodul curent(si el este scos de pe stiva). Nodurile scoase reprezinta nodurile ce compun CTC.
*
* Repet procesul pana cand gasesc toate CTC
*
* Afisez nodurile ce alcatuiesc toate CTC
* Pentru infoarena am sortat crescator vectorul ce contine nodurile din CTC. In cel mai rau caz, complexitatea ar deveni O(n*log(n)),
* daca tot graful ar fi o componenta tare conexa. Fiind doar o chestiune de afisare am considerat ca nu ar fi necesara mentionarea sa
* in ceea ce priveste complexitatea de timp mentionata in prima linie a comentariului
* */
#include <fstream>
#include <vector>
#include <stack>
#include <algorithm>
using namespace std;
#define min(a, b) ( (a < b) ? a : b) // macro-ul este putin mai rapid
ifstream in("ctc.in");
ofstream out("ctc.out");
int incide = 1; // ID-ul folosit pentru detectarea CTC
void dfs(vector<vector<int>> & graph, vector<int> & id, vector<int> & low, stack<int> & s, vector<bool> &inStack, vector<vector<int>> & sol, int k){
inStack[k] = true;
low[k] = id[k] = incide++; // id-ul reprezinta la al catelea nod in parcurgere am ajuns
s.push(k); // adaug pe stiva nodul la care sunt
for (auto x : graph[k]){ // parcurg vecinii lui k
if(!id[x]) // daca id-ul este 0, atunci nodul este nevizitat
dfs(graph, id, low, s, inStack, sol, x), low[k] = min(low[k], low[x]); // DFS + minimul dintre nodul curent(k) si vecinul sau(x) cand vine vorba de label
else if(inStack[x]) // daca nodul vecin(x) este vizitat si se afla pe stiva, atunci nodul curent ia valoarea minima dintre label-ul curent si label-ul vecinului sau(x)
low[k] = min(low[k], low[x]);
}
if(id[k] == low[k]){ // daca label-ul (id-ul cel mai mic din componenta conexa) corespunde id-ului, atunci am descoperit componenta tare conexa
vector<int> tmp;
while(s.top() != k) // golesc toate elementele asociate CTC-ului din stiva
inStack[s.top()] = 0, tmp.push_back(s.top()) , s.pop();
inStack[s.top()] = 0, tmp.push_back(s.top()), s.pop(); // construiesc un vector temporar care contine nodurile din CTC
sol.push_back(tmp); // Adaug vectorul obtinut la solutie
tmp.clear();
}
}
int main(){
int n, m, x, y;
in >> n >> m;
vector<vector<int>> graph(n +1), sol;
vector<int> id(n+1, 0), low(n+1, 0);
vector<bool> boolV(n+1, 0);
stack<int> s;
while(m--){
in >> x >> y;
graph[x].push_back(y); // graf orientat
}
in.close();
for(int i = 1; i <= n; i++) // parcurg toate nodurile
if(!id[i]) // apelez DFS pt nodurile nevizitate
dfs(graph, id, low,s, boolV, sol, i);
out << sol.size() << '\n';
for (auto x : sol) {
sort(x.begin(), x.end()); // sortez pentru ca nodurile din CTC sa fie prezentate in ordine crescatoare
for (auto y: x) {
out << y << ' ';
}
out << '\n';
}
out.close();
return 0;
}