#include <fstream>
using namespace std;
ifstream fin("ctc.in");
ofstream fout("ctc.out");
const int NMAX = 100005;
const int MMAX = 200005;
int n, m;
// Liste de adiacenta cu vectori statici (graf orientat).
int lst[NMAX], vecin[MMAX], urm[MMAX], nrm;
int disc[NMAX]; // momentul descoperirii in DFS (0 = nevizitat)
int low[NMAX]; // cel mai mic disc accesibil din u prin noduri inca aflate in stiva
int timp;
int stiva[NMAX], vf; // nodurile vizitate care nu au fost inca puse intr-o componenta
bool inStiva[NMAX];
int comp[NMAX], nrc; // componenta fiecarui nod
// Pentru afisare: lista nodurilor din fiecare componenta.
int primul[NMAX], urmInComp[NMAX];
void adauga(int u, int v) {
nrm++;
vecin[nrm] = v;
urm[nrm] = lst[u];
lst[u] = nrm;
}
// Algoritmul lui Tarjan
void dfs(int u) {
disc[u] = low[u] = ++timp;
stiva[++vf] = u;
inStiva[u] = true;
for (int k = lst[u]; k != 0; k = urm[k]) {
int v = vecin[k];
if (disc[v] == 0) {
dfs(v);
if (low[v] < low[u]) low[u] = low[v];
} else if (inStiva[v] && disc[v] < low[u]) {
low[u] = disc[v]; // v e in stiva => e in aceeasi componenta cu u
}
}
// u este "radacina" unei componente: toate nodurile din stiva de deasupra lui (inclusiv u)
// formeaza o componenta tare conexa.
if (low[u] == disc[u]) {
nrc++;
int v;
do {
v = stiva[vf--];
inStiva[v] = false;
comp[v] = nrc;
urmInComp[v] = primul[nrc];
primul[nrc] = v;
} while (v != u);
}
}
int main() {
fin >> n >> m;
for (int e = 1; e <= m; e++) {
int x, y;
fin >> x >> y;
adauga(x, y);
}
for (int i = 1; i <= n; i++)
if (disc[i] == 0) dfs(i);
fout << nrc << "\n";
for (int c = 1; c <= nrc; c++) {
for (int v = primul[c]; v != 0; v = urmInComp[v])
fout << v << " ";
fout << "\n";
}
return 0;
}