#include using namespace std; const int N = 1e2 + 10, M = N << 1; //邻接表 int e[M], h[N], idx, ne[M]; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++; } int dfn[N], low[N]; // u节点被访问的时间点/ u通过有向边,可回溯到的最早的时间点 int stk[N], top; // 栈数组stk,栈顶指针top bool in_stk[N]; // 是不是在栈中 int timestamp; // 时间戳 int scc; // 连通块个数 void init_tarjan(int n) { // 封装的tarjan算法初始化函数 //初始化 timestamp = scc = top = 0; memset(in_stk, false, sizeof(in_stk)); memset(dfn, 0, sizeof(dfn)); } void tarjan(int u) { dfn[u] = low[u] = ++timestamp; //分配被访问的时间点dfn[u] 和 u通过有向边可回溯到最早的时间点 stk[++top] = u; // u节点入栈 in_stk[u] = true; //记录u在栈中 for (int i = h[u]; ~i; i = ne[i]) { //遍历u的所有出边 int j = e[i]; if (!dfn[j]) { //如果j没有被访问过 tarjan(j); //深度搜索,递归访问它 //等孩子整完事,用一下孩子的结果 尝试更新自己 low[u] = min(low[u], low[j]); //类似后序遍历,u通过有向边可回溯到的最早时间可能更新为它儿子可以获得的最早时间点 } else if (in_stk[j]) //被访问过,但不在栈中,比如视频2中提到的e,已经出栈,是独立的强连通分量了,就不需要处理,只需要处理b low[u] = min(low[u], low[j]); //在栈中,可以利用j更新u的回溯值,j是u的祖先 } //如视频所讲,发现u的被访问时间点dfn[u]和可回溯最早时间点low[u]一致,说明它不能回溯到更早,需要把栈顶到它之间所有点出栈 if (dfn[u] == low[u]) { // u节点就是进入强连通分量的入口点 ++scc; // 连通块个数++,如视频 d c b组成了一个强连通分量 int x; // 从栈顶到b的所有点组成了一个强连通分量 do { x = stk[top--]; in_stk[x] = false; } while (x != u); } //能有机会从栈中批量弹出的,是一组能构成强连通分量的节点。而,有的节点因为不与其它节点构建强连通分量,就永远无法从栈中弹出 //如视频所说,a就是永远在栈中,它是一个孤立的强连通分量。 //如视频(复杂的例子)所说,e点是独立的强连通分量,它满足dfn[u]=low[u],它是独立的强连通分量,scc++,同时 x=stk[top--] } // 调用示例 // for (int i = 0; i < n; i++) // if (!dfn[i]) tarjan(i); int main() { int n, m; while (~scanf("%d%d", &n, &m) && n) { idx = 0; memset(h, -1, sizeof(h)); while (m--) { int a, b; scanf("%d%d", &a, &b); add(a, b); } //初始化 init_tarjan(n); for (int i = 0; i < n; i++) if (!dfn[i]) tarjan(i); //强连通分量数量等于节点数,说明无环,并且是一个连通图 scc == n ? puts("YES") : puts("NO"); } return 0; }