#include <iostream>
#include <map>
#include <set>
#include <algorithm>
#include <vector>
#include <sstream>
#include <string>
#include <functional>
#include <queue>
#include <deque>
#include <stack>
#include <limits>
#include <unordered_map>
#include <unordered_set>
#include <cmath>
#include <fstream>
#include <iterator>
#include <random>
#include <chrono>


#define forr(i,start,count) for (int i = (start); i < (start)+(count); ++i)
#define set_map_includes(set, elt) (set.find((elt)) != set.end())
#define readint(i) int i; cin >> i
#define readll(i) ll i; cin >> i
#define readdouble(i) double i; cin >> i
#define readstring(s) string s; cin >> s

typedef long long ll;

using namespace std;

ll modd = 1000 * 1000 * 1000 + 7;

template<class T>
class depth_first_search_iterator {
  public:
    stack<pair<T,T>> todo;
    function<bool(T)> notvisited;
    function<void(T)> mark_visited;
    function<set<T>(T)> neighbors;

    depth_first_search_iterator() {}

    depth_first_search_iterator(T start, function<bool(T)> notv, function<void(T)> mark_v, function<set<T>(T)> neigh) :
      notvisited(notv), mark_visited(mark_v), neighbors(neigh), end_(false), current(start), prev(start) {
          todo.push(make_pair(start, start)); operator++();
    }

    void operator++() {
        end_ = true;
        while (!todo.empty()) {
            current = todo.top().first; prev = todo.top().second; todo.pop();
            if (notvisited(current)) {
                mark_visited(current);
                for (const T x : neighbors(current)) {  todo.push(make_pair(x, current));   }
                end_ = false;
                break;
            }
        }
    }

    T operator*() {     return current;    }

    T previous() {  return prev;  }

    bool end() {  return end_;  }

  private:
    T current; // contains current node
    T prev;    // contains previous node
    bool end_;
};

template<class T>
class breadth_first_search_iterator {
  public:
    queue<pair<T,T>> todo;
    function<bool(T)> notvisited;
    function<void(T)> mark_visited;
    function<set<T>(T)> neighbors;

    breadth_first_search_iterator() {}

    breadth_first_search_iterator(T start, function<bool(T)> notv, function<void(T)> mark_v, function<set<T>(T)> neigh) :
      notvisited(notv), mark_visited(mark_v), neighbors(neigh), end_(false), current(start), prev(start) {
          todo.push(make_pair(start, start)); operator++();
    }

    void operator++() {
        end_ = true;
        while (!todo.empty()) {
            current = todo.front().first; prev = todo.front().second; todo.pop();
            if (notvisited(current)) {
                mark_visited(current);
                for (const T x : neighbors(current)) {  todo.push(make_pair(x, current));   }
                end_ = false;
                break;
            }
        }
    }

    T operator*() {     return current;    }

    T previous() {  return prev;  }

    bool end() {  return end_;  }

  private:
    T current;   // current node
    T prev;      // previous node
    bool end_;
};

template<class T>
class DirectedGraph {
    // allows only single connection between two vertices
    public:
      vector<T> vertices;
      vector<set<int>> neighbors;
      depth_first_search_iterator<int> dfs_iterator;
      breadth_first_search_iterator<int> bfs_iterator;
      vector<bool> visited;

      DirectedGraph() {}

      DirectedGraph(int n, T default_val = 0) {  for (int i = 0; i < n; ++i) {  AddVertex(default_val);  }   }

      bool EdgeExists(int i_from, int j_to) {
          return (neighbors[i_from].find(j_to) != neighbors[i_from].end());
      }

      void AddVertex(T val) {
          vertices.push_back(val);
          neighbors.push_back(set<int>());      }

      void AddEdge(int i_from, int j_to) {
          if (!EdgeExists(i_from, j_to)) {     neighbors[i_from].insert(j_to);      }
      }

      T& operator[](int i) {      return vertices[i];      }

      void RemoveEdge(int i_from, int j_to) {
          if (EdgeExists(i_from, j_to)) {
          neighbors[i_from].erase(find(neighbors[i_from].begin(), neighbors[i_from].end(), j_to));      } }

      DirectedGraph<T> Transpose() {
          DirectedGraph<T> ret;
          for (auto x : vertices) {  ret.AddVertex(x);   }
          for (int i_from = 0; i_from < vertices.size(); ++i_from) {
              for (int j_to : neighbors[i_from]) {
                  ret.AddEdge(j_to, i_from);
              }
          }
          return ret;      }

      int vertices_count() {   return vertices.size();  }

      void dfs_init(int start) {
          visited = vector<bool>(vertices.size(), false);
          dfs_iterator = depth_first_search_iterator<int>(start, [this](int x){ return !visited[x]; }, [this](int x) { visited[x] = true;  },
            [this](int x) {  return neighbors[x];  } );
      }

      void bfs_init(int start) {
          visited = vector<bool>(vertices.size(), false);
          bfs_iterator = breadth_first_search_iterator<int>(start, [this](int x){ return !visited[x]; }, [this](int x) { visited[x] = true;  },
            [this](int x) {  return neighbors[x];  } );
      }

      void print_() {
          forr(i, 0, vertices.size()) {
              for (auto x : neighbors[i]) {
                  cout << i << "->" << x << endl;
              }
          }
      }

};

class UnionFind {
    public:
      vector<int> parent;
      vector<int> size;
//      size[i] = number of nodes in tree rooted at i
// Note: not necessarily correct if i is not a root node
      set<int> roots;

    UnionFind(int n) : parent(n), size(n) {
        forr(i,0,n) { roots.insert(i); }
        forr(i,0,n) {
            parent[i] = i; size[i] = 1;
        }
    } 

    int Find(int p) {
        int root = p;
        while (root != parent[root]) {  root = parent[root]; }
        while (p != root) {
            int newp = parent[p];
            parent[p] = root;
            p = newp;
        }
        return root;
    }

    void Union(int p, int q) {
        int rootP = Find(p);
        int rootQ = Find(q);
        if (rootP == rootQ) return;

        if (size[rootP] < size[rootQ]) {
            parent[rootP] = rootQ;
            size[rootQ] += size[rootP];
            roots.erase(rootP);
        }
        else {
            parent[rootQ] = rootP;
            size[rootP] += size[rootQ];
            roots.erase(rootQ);
        }
    }

};


long long time__;

template<class T>
void SCCUtil(int u, vector<int>& disc, vector<int>& low, stack<int>& st, vector<bool>& stackMember, UnionFind& ret, DirectedGraph<T>& gr)   { 
  
        disc[u] = low[u] = ++time__;
        st.push(u);
        stackMember[u] = true; 

        for (auto v : gr.neighbors[u])    {
          if (disc[v] == -1)   { 
              SCCUtil<T>(v, disc, low, st, stackMember, ret, gr);
              low[u]  = min(low[u], low[v]); 
          }
          else if (stackMember[v])    { low[u]  = min(low[u], disc[v]); }   }
  
        int w = 0;
        if (low[u] == disc[u])   {
          stack<int> component;
          while (st.top() != u)    { 
              w = (int) st.top();
              component.push(w);
              stackMember[w] = false; 
              st.pop(); 
          } 
          w = (int) st.top();
          while (!component.empty())   {  ret.Union(w, component.top()); component.pop();   }

          stackMember[w] = false;
          st.pop(); 
        }
};

template<class T>
UnionFind SCC(DirectedGraph<T>& gr)  {
        vector<int> disc(gr.vertices.size(), -1);
        vector<int> low(gr.vertices.size(), -1); 
        vector<bool> stackMember(gr.vertices.size(), false);
        stack<int> st;
        UnionFind ret(gr.vertices.size());

        time__ = 0;
        for (int i = 0; i < gr.vertices.size(); i++)  {    if (disc[i] == -1) {     SCCUtil<T>(i, disc, low, st, stackMember, ret, gr); }        }
        return ret;
};

template<class T>
pair<DirectedGraph<int>, UnionFind> CondensedGraph(DirectedGraph<T>& gr) {
    // returns condensed graph -- vertices are scc components, *value* (not index) of each vertex is index of root in unionfind
    DirectedGraph<int> ret;
    UnionFind ret_ = SCC<T>(gr);
    map<int,int> dict;
    int i = 0;
    for (auto x : ret_.roots) {      ret.AddVertex(x); dict[x] = i; ++i;  }
    for (int i_from = 0; i_from < gr.vertices.size(); ++i_from) {
        for (int j_to : gr.neighbors[i_from]) {
            int root_i = ret_.Find(i_from), root_j = ret_.Find(j_to);
            if (root_i != root_j)  {      ret.AddEdge(dict[root_i], dict[root_j] );         }
        }
    }
    return make_pair(ret, ret_);
};

template<class T>
vector<int> TopologicalOrder(DirectedGraph<T>& g) {
    // returns indices, sorted topologically for acyclic directed graph

    vector<int> ret;
    vector<int> incoming(g.vertices.size(), 0);
    forr(i, 0, g.vertices.size()) {
        for (auto e : g.neighbors[i]) {
            ++incoming[e];
        }
    }

    set<pair<int,int>> next_vertex;
    forr(i,0,incoming.size()) {  next_vertex.insert(make_pair(incoming[i], i));   }

    while (!next_vertex.empty()) {
        ret.push_back(next_vertex.begin()->second);
        next_vertex.erase(next_vertex.begin());
        for (auto e : g.neighbors[ret.back()]) {
            if (incoming[e] > 0) {
              next_vertex.erase(make_pair(incoming[e], e));
              --incoming[e];
              next_vertex.insert(make_pair(incoming[e], e));
            }
        }
    }

    return ret;
};


int main()   {

      ios_base::sync_with_stdio(false);

      cout.precision(17);
      mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
      uniform_int_distribution<int> rand_gen(0, modd);   // rand_gen(rng) gets the rand no

//    readint(test_cases);
    int test_cases = 1;
    forr(t, 1, test_cases) {
        readint(n); readint(m);
        DirectedGraph<int> g(n);
        forr(i,0,m) {
            readint(a); readint(b);
            g.AddEdge(a,b);
        }
        auto q = CondensedGraph(g);
        auto ord = TopologicalOrder(q.first);

        cout << q.first.vertices_count() << endl;
        map<int, vector<int>> solution;
        forr(i,0,n) {
            solution[q.second.Find(i)].push_back(i);
        }
        for(auto x : ord) {
            cout << solution[q.first[x]].size() << " ";
            for(auto x : solution[q.first[x]]) {
                cout << x << " ";
            }
            cout << endl;
        }        

    }
    return 0;
}


Measured memory_bytes: 227832