强连通分量模板

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <vector>
#include <stack>
using namespace std;
const int maxn = 2000 + 7;
const int INF = ~0U >> 1;
vector<int> G[maxn];
int n, m, k = 0, cnt = 0;
int low[maxn], dfn[maxn], in[maxn];
stack<int> s;

void tarjan(int u) {
    low[u] = dfn[u] = ++k;
    s.push(u);
    in[u] = 1;
    for(int i = 0; i < G[u].size(); ++i) {
        int v = G[u][i];
        if(!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        } else if(in[v]) {
            low[u] = min(low[u], dfn[v]);
        }
    }
    int v;
    if(dfn[u] == low[u]) {
        ++cnt;
        do {
            v = s.top(); s.pop();
            in[v] = 0;
        } while(u != v);
    }
}

int main() {
    scanf("%d%d", &n, &m);
    int u, v;
    for(int i = 0; i < m; ++i) {
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
    }
    for(int i = 1; i <= n; ++i) {
        if(!dfn[i]) tarjan(i);
    }
    printf("%d\n", cnt);
    return 0;
}


参考地址:点击打开链接

Tarjan算法的操作原理如下:

  1. Tarjan算法基于定理:在任何深度优先搜索中,同一强连通分量内的所有顶点均在同一棵深度优先搜索树中。也就是说,强连通分量一定是有向图的某个深搜树子树。
  2. 可以证明,当一个点既是强连通子图Ⅰ中的点,又是强连通子图Ⅱ中的点,则它是强连通子图Ⅰ∪Ⅱ中的点。
  3. 这样,我们用low值记录该点所在强连通子图对应的搜索子树的根节点的Dfn值。注意,该子树中的元素在栈中一定是相邻的,且根节点在栈中一定位于所有子树元素的最下方。
  4. 强连通分量是由若干个环组成的。所以,当有环形成时(也就是搜索的下一个点已在栈中),我们将这一条路径的low值统一,即这条路径上的点属于同一个强连通分量。
  5. 如果遍历完整个搜索树后某个点的dfn值等于low值,则它是该搜索子树的根。这时,它以上(包括它自己)一直到栈顶的所有元素组成一个强连通分量。

### 强连通分量缩点算法模板 强连通分量(Strongly Connected Component, SCC)是指在一个有向图中,任意两个节点都可以互相到达的最大子图。通过 Tarjan 算法可以高效地找到这些强连通分量并将其缩成单个节点,从而简化原图结构。 以下是基于 Tarjan 算法的 C++ 实现代码模板: ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 1e5 + 5; // 节点数量上限 int n, m; vector<int> adj[MAXN]; // 邻接表存储图 stack<int> stk; // 辅助栈 bool in_stack[MAXN]; // 判断当前节点是否在栈中 int dfn[MAXN], low[MAXN]; // 时间戳数组和追溯值数组 int scc_id[MAXN]; // 存储每个节点所属的强连通分量编号 int idx = 0, cnt_scc = 0; // 当前时间戳计数器 和 强连通分量计数器 void tarjan(int u) { dfn[u] = low[u] = ++idx; // 初始化dfn和low stk.push(u); // 将u压入栈 in_stack[u] = true; // 标记u已在栈中 for (auto &v : adj[u]) { // 枚举所有邻接点 if (!dfn[v]) { // 如果v未访问过 tarjan(v); low[u] = min(low[u], low[v]); // 更新low[u] } else if (in_stack[v]) { // 如果v已经在栈中 low[u] = min(low[u], dfn[v]); } } if (dfn[u] == low[u]) { // 找到一个新的强连通分量 ++cnt_scc; // 新增一个SCC编号 while (true) { // 提取该SCC中的所有节点 int v = stk.top(); stk.pop(); in_stack[v] = false; scc_id[v] = cnt_scc; // 记录属于哪个SCC if (v == u) break; // 当弹出的是u时结束循环 } } } // 主函数调用部分 void solve() { cin >> n >> m; // 输入节点数和边数 for (int i = 1; i <= m; ++i) { int u, v; cin >> u >> v; // 输入一条有向边 adj[u].push_back(v); } memset(dfn, 0, sizeof(dfn)); // 初始化dfn数组 memset(in_stack, 0, sizeof(in_stack)); idx = cnt_scc = 0; for (int i = 1; i <= n; ++i) { // 对每个节点运行tarjan if (!dfn[i]) tarjan(i); } cout << "Total Strongly Connected Components: " << cnt_scc << "\n"; } ``` #### 关键说明 上述代码实现了 Tarjan 算法的核心逻辑[^2]。 - `dfn` 数组记录了每个节点第一次被访问的时间戳。 - `low` 数组用于追踪能够回溯到的最早祖先节点的时间戳。 - 使用辅助栈来保存当前路径上的节点,并判断哪些节点已经形成一个完整的强连通分量。 - 每次当发现某个节点满足条件 `dfn[u] == low[u]` 时,表示找到了一个新的强连通分量[^3]。 完成 Tarjan 算法后,可以通过构建新的缩点后的图来进行进一步处理。具体做法如下: - 创建新图,其中每个节点代表原始图的一个强连通分量。 - 统计每条边连接的不同强连通分量之间的关系,忽略内部重复边。 --- ### 缩点后的应用实例 假设我们需要计算拓扑序或者解决某些动态规划问题,在缩点之后的新图上操作会更加简单有效。例如,对于 DAG 上的经典 DP 或最短路问题,可以直接在此基础上展开分析。 ```cpp vector<vector<int>> new_adj(cnt_scc + 1); // 缩点后的新图 long long indegree[cnt_scc + 1] = {}; // 入度统计 for (int u = 1; u <= n; ++u) { for (auto &v : adj[u]) { if (scc_id[u] != scc_id[v]) { // 只考虑跨不同SCC的边 new_adj[scc_id[u]].push_back(scc_id[v]); indegree[scc_id[v]]++; } } } ``` 此段代码展示了如何根据原有图生成缩点后的图结构[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值