#include <iostream>
#include <fstream>
#include <vector>
#include <queue>
#include <stack>
#define MAX 100001
using namespace std;
ifstream fin ("ctc.in");
ofstream fout("ctc.out");
class Graf{
public:
//date membre
vector <int> A[MAX]; //liste de adiacenta
int mM, mN;
//constructor
Graf(int N, int M){
mN = N;
mM = M;
}
void BFS();
void DFS(int start, vector<bool> &viz, stack<int> &S);
void ComponenteConexe();
void ComponenteBiconexe();
void DFS_Biconex(int start, int father, vector <bool> &viz, vector <int> &up, vector <int> &nivel, stack <int> &S, int & nrComp, vector<vector<int>> &compBiconexe);
void AfisareBiconex(int nrComp, vector<vector<int>> &compBiconexe);
void ComponenteTareConexe();
void DFST(vector<vector<int>> AT, int start, int comp, vector<bool> &viz, vector<vector<int>> &compTareConexe);
};
void Graf :: BFS(){
int x, y, start;
fin >> start;
for(int i = 1; i <= mM; i++){
fin >> x >> y;
A[x].push_back(y);
}
bool viz[MAX] = {false};
queue <int> Q;
int lg[MAX] = {0}; // lungimea drumului
// vizitam nodul curent
viz[start] = true;
// il punem in coada
Q.push(start);
// fout<<1;
while ( !Q.empty() ){
x = Q.front();
Q.pop();
// preluam primul element din coada apoi il eliminam din coada
for(int i = 0; i < A[x].size(); i++){
// parcurgem toti vecinii lui x
if(viz[A[x][i]] == 0){ // daca nu am mai vizitat vecinul asta{
Q.push(A[x][i]);
// il vizitam
viz[A[x][i]] = 1;
// creste lungimea drumului
lg[A[x][i]] = lg[x] + 1;
}
}
}
for(int i = 1; i <= mN; i++)
if(viz[i] != 0)
fout << lg[i] << " ";
else
fout << -1 << " ";
}
void Graf::DFS(int start, vector<bool> &viz, stack<int> &S) {
viz[start] = true;
// vizitam nodul din care plecam
for(int i = 0; i < A[start].size(); i++){
// parcurgem toti vecinii nodului start
if(!viz[A[start][i]] ){
// daca nu am mai vizitat acest vecin
DFS(A[start][i], viz, S);
}
}
S.push(start);
}
void Graf::ComponenteConexe() {
int x, y;
stack <int> S;
for(int i = 1; i <= mM; i++){
fin >> x >> y;
A[x].push_back(y);
A[y].push_back(x);
}
vector<bool> viz(mN+1, false);
int nrComp = 0;
for(int i = 1; i <= mN; i++){
if(!viz[i]){
nrComp++;
DFS(i,viz, S);
}
}
fout << nrComp;
}
void Graf::DFS_Biconex(int start, int father, vector<bool> &viz, vector<int> &up, vector<int> &nivel, stack<int> &S,
int &nrComp, vector<vector<int>> &compBiconexe) {
viz[start] = true;
// vizitam nodul din care plecam
up[start] = nivel[start];
S.push(start); // adaugam startul in stiva
for ( auto i : A[start] ){
// parcurgem toti vecinii nodului start
if ( i == father ) continue;
//daca nu e nodul din care am venit
if ( viz[i] ){
// daca am mai vizitat acest vecin
up[start] = min(up[start], nivel[i]);
}
else{
nivel[i] = nivel[start] + 1;
DFS_Biconex(i, start, viz, up, nivel, S, nrComp,compBiconexe);
if ( up[i] >= nivel[start] ){
nrComp++;
while ( !S.empty() && S.top() != i ){
compBiconexe[nrComp].push_back(S.top());
S.pop();
}
compBiconexe[nrComp].push_back(S.top());
S.pop();
compBiconexe[nrComp].push_back(start);
}
up[start] = min(up[start], up[i]);
}
}
}
void Graf::AfisareBiconex(int nrComp, vector<vector<int>> &compBiconexe) {
fout << nrComp << "\n";
for ( int i = 1; i <= nrComp; i++ ){
for ( auto j : compBiconexe[i] )
fout << j << " ";
fout << "\n";
}
}
void Graf::ComponenteBiconexe() {
int x, y;
vector <vector<int>> compBiconexe(mN+1);
// bool viz[MAX] = {false};
vector <bool> viz(mN+1,0);
int nrComp = 0;
// int up[MAX] = {0};
vector <int> up(mN+1, 0);
// int nivel[MAX] = {0};
vector <int> nivel(mN+1, 0);
// int S[MAX] = {0};
stack <int> S;
// int top = 0;
for(int i = 1; i <= mM; i++){
fin >> x >> y;
A[x].push_back(y);
A[y].push_back(x);
}
DFS_Biconex(1,0, viz, up, nivel, S, nrComp, compBiconexe);
AfisareBiconex(nrComp, compBiconexe);
}
void Graf::DFST(vector<vector<int>> AT, int start, int comp, vector<bool> &viz, vector<vector<int>> &compTareConexe) {
viz[start] = true;
// fout << 1;
compTareConexe[comp].push_back(start);
for(int i = 0; i < AT[start].size(); i++){
if(!viz[AT[start][i]] )
DFST(AT, AT[start][i], comp, viz, compTareConexe);
}
}
void Graf::ComponenteTareConexe() {
vector <bool> viz(mN+1, false);
//graful transpus
vector<vector<int>> AT(mN + 1);
//componente tare conexe
vector<vector<int>> compTareConexe(mN+1);
//stiva
stack<int> ST;
//nr componente tare conexe
int nrcomp = 0;
int x, y;
for(int i = 1; i <= mM; i++){
fin >> x >> y;
A[x].push_back(y);
AT[y].push_back(x);
}
for(int i = 1; i <= mN; i++){
if(!viz[i]){
DFS(i, viz, ST);
}
}
vector<bool> vizT(mN+1, false);
//luam elementele in ordinea inversa a parcurgerii dfs
//si aplicam dfs pe graful transpus
while(!ST.empty()){
x = ST.top();
ST.pop();
if(!vizT[x]){
nrcomp ++;
DFST(AT, x, nrcomp, vizT, compTareConexe);
}
}
fout << nrcomp << "\n";
for(int i = 1; i <= nrcomp; i++){
for(int j = 0; j < compTareConexe[i].size(); j++){
fout << compTareConexe[i][j] << " ";
}
fout << "\n";
}
}
int main(){
int N, M;
fin >> N >> M;
// fout << 1;
Graf G(N,M);
G.ComponenteTareConexe();
return 0;
}