总结自《算法竞赛入门经典——训练指南》(刘汝佳),具体分析请详见书中解析。
时间戳:说白了就是记录下访问每个结点的次序。假设我们用 pre 保存,那么如果 pre[u] > pre[v], 那么就可以知道先访问的 v ,后访问的 u 。
现在给定一条边, (u, v), 且 u 的祖先为 fa, 如果有 pre[v] < pre[u] && v != fa, 那么 (u, v) 为一条反向边。
无向图的割顶和桥:
求割顶:
#include <iostream> #include <cstdlib> #include <cstdio> #include <algorithm> #include <cstring> #include <stack> #include <vector> #include <queue> #include <map> using namespace std; const int maxn = 1000; vector<int> G[maxn]; int pre[maxn], dfs_clock, low[maxn], n; bool iscut[maxn]; void init(){ for(int i=0; i<n; i++) G[i].clear(); memset(iscut, false, sizeof(iscut)); memset(pre, 0, sizeof(pre)); dfs_clock = 0; } int dfs(int u, int fa){ //u在DFS树中的父结点是fa int lowu = pre[u] = ++dfs_clock; int child = 0; //子结点数目 for(int i=0; i<G[u].size(); i++){ int v = G[u][i]; if(!pre[v]){ //没有访问过v, 没有必要用vis标记了 child++; int lowv = dfs(v, u); lowu = min(lowu, lowv); //用后代的 low 函数更新 u 的 low 函数 if(lowv >= pre[u]){ iscut[u] = true; } } else if(pre[v] < pre[u] && v != fa){ //(u,v)为反向边 lowu = min(lowu, pre[v]); //用反向边更新 u 的 low 函数 } if(fa < 0 && child == 1) iscut[u] = false; } low[u] = lowu; return lowu; } int main(){ int m, u, v; scanf("%d%d", &n, &m); init(); for(int i=0; i<m; i++){ cin>>u>>v; G[u].push_back(v); G[v].push_back(u); } for(int i=0; i<n; i++)if(!pre[i]){ dfs(i, -1); } for(int i=0; i<n; i++){ //将割点输出 if(iscut[i]) printf("%d ", i); } putchar('\n'); return 0; }