#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int, int> PII;
const int MAXN = 100000 + 10, inf = 1e9;

namespace String {
    int selfmatch(const VI &pat) {
        int n = pat.size(), m = 2 * n - 1, ret = 0;
        VI text(n * 2), fail(n * 2, -1);
        for (int i = 0; i < n; ++ i) text[i] = text[i + n] = pat[i];
        for (int i = 1, j; i < n; ++ i) {
            for (j = fail[i - 1]; j >= 0 && pat[j + 1] != pat[i]; ) j = fail[j];
            fail[i] = (pat[j + 1] == pat[i]) ? ++ j : j;
        }
        for (int i = 0, j = -1; i < m; ++ i) {
            while (j >= 0 && pat[j + 1] != text[i]) j = fail[j];
            if (pat[j + 1] == text[i]) ++ j;
            if (j + 1 == n) ++ ret, j = fail[j];
        }
        return ret;
    }
    void manacher(const VI &s, VI &u, int n) {
        n<<=1; fill(u.begin(), u.end(), 0);
        for (int i=0,j=0,k; i<n; i+=k,j=max(j-k,0)) {
            while (i>=j && i+j+1<n && s[(i-j)>>1]==s[(i+j+1)>>1]) ++j;
            for (u[i]=j,k=1; i>=k && u[i]>=k && u[i-k]!=u[i]-k; ++k) {
                u[i+k]=min(u[i-k],u[i]-k);
            }
        }
    }
    bool palidrome(const VI &s) {
        VI u(s.size() * 2); manacher(s, u, s.size());
        for (int i = 0, n = s.size(); i < n; ++ i) {
            if (u[i] == i + 1 && u[i + n] == n - i - 1) return true;
        }
        return false;
    }
}

struct VectorCmp {
    bool operator() (const VI &A, const VI &B) const {
        if (A.size() == B.size()) return A < B;
        else return A.size() < B.size();
    }
};

map<VI, int, VectorCmp> TreeCache;
vector<VI> circles;
VI G[MAXN], GG[MAXN];
set<PII> removed;
vector<PII> Edge;
int vis[MAXN], cyc[MAXN], dep[MAXN], pre[MAXN];
int size[MAXN], mx[MAXN], Hash[MAXN];
bool prime[MAXN], is_ring[MAXN];
int root, mins, n, m, total, extra_mul;

int getTreeID(const VI &Tree) {
    if (TreeCache.find(Tree) == TreeCache.end()) TreeCache[Tree] = total ++;
    return TreeCache[Tree];
}

void merge(map<int, int> &A, map<int, int> B) {
    for (auto &x : B) A[x.first] += x.second;
}

map<int, int> dfs(int u, int f) {
    VI son, rson;
    map<int, int> ret;
    for (int i = 0; i < (int)GG[u].size(); ++ i) {
        if (GG[u][i] == f) {
            rotate(GG[u].begin(), GG[u].begin() + i, GG[u].end());
            break;
        }
    }
    for (int i = 0; i < (int)GG[u].size(); ++ i) {
        int v = GG[u][i]; if (v == f) continue;
        merge(ret, dfs(v, u));
        son.push_back(Hash[v]);
    }
    if (is_ring[u]) {
        if (u != root) {
            rson = son; reverse(rson.begin(), rson.end());
            if (rson < son) swap(rson, son);
            else if (rson == son) ret[2] ++;
        }
        else {
            extra_mul = String::selfmatch(son);
            if (String::palidrome(son)) ret[2] ++;
        }
    }
    else {
        sort(son.begin(), son.end());
        for (int i = 0, j, n = son.size(); i < n; i = j) {
            for (j = i; j < n && son[i] == son[j]; ++ j) {}
            if (j - i > 1) ret[j - i] ++;
        }
    }
    Hash[u] = getTreeID(son);
    return ret;
}

void findcircle(int u, int d, int f) {
    if (vis[u]) {
        if (d > dep[u]) {
            VI cc; int v = f; cc.push_back(v);
            while (v != -1 && v != u && !cyc[v]) {
                cyc[v] = true; v = pre[v];
                cc.push_back(v);
            }
            circles.push_back(cc);
        }
        return;
    }
    dep[u] = d; vis[u] = true; pre[u] = f;
    for (auto &v : G[u]) {
        if (v != f) findcircle(v, d + 1, u);
    }
}

void getroot(int u, int f) {
    size[u] = 1; mx[u] = 0;
    for (auto &v : GG[u]) {
        if (v == f) continue;
        getroot(v, u); size[u] += size[v];
        mx[u] = max(mx[u], size[v]);
    }
    mx[u] = max(mx[u], n - size[u]);
}

void sieve() {
    memset(prime, 1, sizeof(prime));
    for (int i = 2; i < MAXN; ++ i) {
        if (!prime[i]) continue;
        for (int j = i + i; j < MAXN; j += i) prime[j] = 0;
    }
}

int calc(int n, int p) {
    int ret = 0;
    for (LL pp = p; n / pp; pp *= p) ret += n / pp;
    return ret;
}

void output(map<int, int> &ret) {
    vector<PII> plist, fact;
    for (auto &x : ret) fact.push_back(x);
    for (int i = 2; i < MAXN; ++ i) {
        if (!prime[i]) continue;
        auto it = lower_bound(fact.begin(), fact.end(), PII(i, 0));
        int cnt = 0;
        for (; it != fact.end(); ++ it) {
            cnt += it->second * calc(it->first, i);
        }
        while (extra_mul % i == 0) extra_mul /= i, ++ cnt;
        if (cnt) plist.push_back(PII(i, cnt));
    }
    printf("%d\n", (int)plist.size());
    for (int i = 0; i < (int)plist.size(); ++ i) {
        printf("%d %d\n", plist[i].first, plist[i].second);
    }
}

void rebuild() {
    removed.clear();
    for (int i = 0; i < n; ++ i) GG[i].clear(), is_ring[i] = false;
    for (auto &x : circles) {
        x.push_back(x[0]); GG[n].clear(); is_ring[n] = true;
        for (int i = 1; i < (int)x.size(); ++ i) {
            GG[n].push_back(x[i]);
            GG[x[i]].push_back(n);
            int u = x[i], v = x[i - 1];
            if (u > v) swap(u, v);
            removed.insert(PII(u, v));
        }
        n ++;
    }
    for (auto &x : Edge) {
        int u = x.first, v = x.second;
        if (u > v) swap(u, v);
        if (removed.count(PII(u, v))) continue;
        GG[u].push_back(v); GG[v].push_back(u);
    }
    getroot(0, -1);
    int mins = inf;
    VI rt; rt.clear();
    for (int i = 0; i < n; ++ i) mins = min(mins, mx[i]);
    for (int i = 0; i < n; ++ i) {
        if (mx[i] == mins) rt.push_back(i);
    }
    if (rt.size() == 1) root = rt[0];
    else if (rt.size() == 2) {
        int u = rt[0], v = rt[1];
        root = n ++; GG[root].clear();
        is_ring[root] = false;
        GG[root].push_back(u); GG[root].push_back(v);
        GG[u].erase(find(GG[u].begin(), GG[u].end(), v));
        GG[v].erase(find(GG[v].begin(), GG[v].end(), u));
    }
}

int main() {
    freopen("cactus.in", "r", stdin);
    freopen("cactus.out", "w", stdout);
    sieve();
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++ i) G[i].clear();
    for (int k, u; m --; ) {
        scanf("%d%d", &k, &u); -- u;
        for (int i = 1, v; i < k; u = v, ++ i) {
            scanf("%d", &v); -- v;
            G[u].push_back(v); G[v].push_back(u);
            Edge.push_back(PII(u, v));
        }
    }
    findcircle(0, 0, -1);
    rebuild();
    TreeCache.clear(); total = 0; extra_mul = 1;
    map<int, int> ret = dfs(root, -1);
    output(ret);
    return 0;
}
