#include using namespace std; const int N = 10010, M = 50010; int n, m; // n个点,m条边 int d[N]; // 记录一下每个强连通分量的出度 // 链式前向星 int e[M], h[N], idx, w[M], ne[M]; void add(int a, int b, int c = 0) { e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++; } // Tarjan算法求强连通分量 int stk[N], top, in_stk[N]; // stk[N]:堆栈,top:配合堆栈使用的游标top,in_stk[N]:是否在栈内 int dfn[N], ts, low[N]; // dfn[N]:时间戳记录数组,ts:时间戳游标, low[N]:从u开始走所能遍历到的最小时间戳 int id[N], scc_cnt, sz[N]; // id[N]:强连通分量块的编号,scc_cnt:强连通分量的编号游标,sz[i]:编号为i的强连通分量中原来点的个数 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]) { // v未访问过 tarjan(v); // dfs深搜 low[u] = min(low[u], low[v]); // 更新low[u] } else if (in_stk[v]) // v已经栈中,找到强连通分量 low[u] = min(low[u], dfn[v]); // 更新low[u] } // 发现强连通分量 if (dfn[u] == low[u]) { ++scc_cnt; int x; do { x = stk[top--]; in_stk[x] = 0; id[x] = scc_cnt; // 记录x是缩点后编号为ssc_cnt号强连通分量的一员 sz[scc_cnt]++; // 缩点后编号为ssc_cnt号强连通分量中的成员个数+1 } while (x != u); } } int main() { scanf("%d %d", &n, &m); memset(h, -1, sizeof h); // 初始化邻接表 for (int i = 1; i <= m; i++) { int a, b; scanf("%d %d", &a, &b); add(a, b); } //(1)求强连通分量,缩点 for (int i = 1; i <= n; i++) // 需示枚举每个点出发尝试,否则会出现有的点没有遍历到的情况 if (!dfn[i]) tarjan(i); //(2)缩点后其实就是一个DAG,计算出DAG中每个新节点的出度 for (int u = 1; u <= n; u++) // 枚举原图中每个出边,用法好怪异~ for (int i = h[u]; ~i; i = ne[i]) { int v = e[i]; // u->v int a = id[u], b = id[v]; // u,v分别在哪个强连通分量中 if (a != b) d[a]++; // a号强连通分量,也可以理解为是缩点后的点号,出度++ } //(3) 出度为0的只能有1个,如果大于1个,就无解,如果正好是一个,返回此强连通分量中结点的个数 int zeros = 0, res = 0; for (int i = 1; i <= scc_cnt; i++) // 枚举强连通分量块 if (!d[i]) { // 如果出度是0 zeros++; // 出度是0的强连通分量个数+1 res = sz[i]; // 累加此强连通分量中点的个数 if (zeros > 1) { // 如果强连通分量块的数量大于1个,则无解 res = 0; break; } } //(4)求有多少头牛被除自己之外的所有牛认为是受欢迎的 printf("%d\n", res); return 0; }