#include using namespace std; typedef long long LL; const int N = 100010, M = 2000010; // 因为要建新图,两倍的边 int n, m, mod; // 点数、边数、取模的数 int f[N], g[N]; int h[N], hs[N], e[M], ne[M], idx; // h: 原图;hs: 新图 void add(int h[], int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } // tarjan求强连通分量 int dfn[N], low[N], ts, in_stk[N], stk[N], top; int id[N], scc_cnt, sz[N]; void tarjan(int u) { dfn[u] = low[u] = ++ts; stk[++top] = u; in_stk[u] = 1; for (int i = h[u]; ~i; i = ne[i]) { int v = e[i]; if (!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } else if (in_stk[v]) low[u] = min(low[u], dfn[v]); } if (dfn[u] == low[u]) { ++scc_cnt; int x; do { x = stk[top--]; in_stk[x] = 0; id[x] = scc_cnt; sz[scc_cnt]++; } while (x != u); } } int main() { memset(h, -1, sizeof h); // 原图 memset(hs, -1, sizeof hs); // 新图 scanf("%d %d %d", &n, &m, &mod); while (m--) { int a, b; scanf("%d %d", &a, &b); add(h, a, b); } // (1) tarjan算法求强连通分量 for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i); // (2) 缩点,建新图 unordered_set S; // 对连着两个不同强连通分量的边进行判重 for (int u = 1; u <= n; u++) for (int i = h[u]; ~i; i = ne[i]) { int v = e[i]; int a = id[u], b = id[v]; LL hash = a * 1000000ll + b; // 新两点a,b之间的Hash值,防止出现重边 if (a != b && !S.count(hash)) { // ① 不同的连通块 ② 非重边 add(hs, a, b); // 加入新图 S.insert(hash); // 记录a,b端点的边使用过 } } // (3) tarjan统计出来的前连通分量是逆拓扑序的 // 计算以每个新点(强连通分量)为终点的最长链中的点的数目f[i]和数量g[i] for (int u = scc_cnt; u; u--) { // 因此dp的初始点就在id的最后一个,因此我们逆着id做就是拓扑序了 if (!f[u]) { // u节点被初次访问时 f[u] = sz[u]; // 最长链中点数量=存储u号强连通分量中节点的个数 g[u] = 1; // 最长链的条数=1条 } // 对DAG开始进行递推,层次感好强 for (int i = hs[u]; ~i; i = ne[i]) { int v = e[i]; if (f[v] < f[u] + sz[v]) { f[v] = f[u] + sz[v]; // 最大值被打破(联想数字三角形) g[v] = g[u]; // 同步更新条数 } else if (f[v] == f[u] + sz[v]) // 最大值被追平,则叠加条数 g[v] = (g[v] + g[u]) % mod; } } // (4) 求解答案 int res = 0, sum = 0; // 最大半连通子图节点数、对应方案数 for (int i = 1; i <= scc_cnt; i++) if (f[i] > res) res = f[i], sum = g[i]; else if (f[i] == res) sum = (sum + g[i]) % mod; // 输出 printf("%d\n%d\n", res, sum); return 0; }