[gotoac]强连通+缩点

本文介绍了一种基于Tarjan算法实现的强连通分量(SCC)查找方法,该方法能够有效地将图中所有节点按强连通分量进行划分,并通过缩点操作计算新的有向无环图(DAG),用于进一步的图分析任务。
struct Node{
    int v,next;
}edge[M<<1];//原图的边
int now[M],init[M];//新图的邻接表表头,原图的邻接表表头
int pre[M],Index;//访问顺序
int low[M],idx[M],hash[M];//追溯的最早次序,点在新图的id,哈希新图的边
int ss[M],top;//放节点的栈
int n , nn , len;//原图大小,新图大小,边数(旧+新)
int m,_in[M],_out[M];//旧边数,新图入度,新图出度
 
void dfs(int u) {
    pre[u] = low[u] = Index ++;
    ss[++top] = u;
    for(int i = init[u] ; i != -1 ; i = edge[i].next) {
        int v = edge[i].v;
        if(pre[v] == -1) {
            dfs(v);
            low[u] = min(low[u] , low[v]);
        } else if(idx[v] == -1) {
            low[u] = min(low[u] , pre[v]);
        }
    }
    if(pre[u] == low[u]) {
        int v = -1;
        nn++;
        while(u != v) {
            v = ss[top--];
            idx[v] = nn;
        }
        now[nn] = -1;
    }
}
 
void Trajan() {
    memset(pre,-1,sizeof(int)*(n+1));
    memset(idx,-1,sizeof(int)*(n+1));
    Index = top = nn = 0;
    ss[0] = -1;
    for(int i=1;i<=n;i++) if(pre[i] == -1) dfs(i);
}
 
void shrink(){//缩点+求出缩点之后的入度出度
    memset(_in,0,sizeof(int)*(nn+1));
    memset(_out,0,sizeof(int)*(nn+1));
    memset(hash,-1,sizeof(int)*(nn+1));
    for(int u=1;u<=n;u++) {
        for(int i = init[u] ; i != -1 ; i = edge[i].next) {
            int v = edge[i].v;
            if(idx[u] != idx[v] && hash[idx[u]] != idx[v]) {
                hash[idx[u]] = idx[v];
                edge[len].next = now[ idx[u] ];
                edge[len].v = idx[v];
                now[ idx[u] ] = len++;
 
                _in[idx[v]]++,_out[idx[u]]++;
            }
        }
    }
}

### 连通分量算法模板 连通分量(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]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值