总结自《算法竞赛入门经典——训练指南》(刘汝佳),具体分析请详见书中解析。

 

时间戳:说白了就是记录下访问每个结点的次序。假设我们用 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;
}
View Code

相关文章: