Tarjan割边
代码讲解:
#include<bits/stdc++.h> using namespace std; const int M = 2e5 + 10, N = 1e4 + 10; struct edge{int x, y;}; // 这里采用邻接矩阵进行存图,可以将存边用的edge替换成pair<int, int>,方便排序 vector<edge> e; //记录边集,将每条边进行编号(即对应e的下标),h数组里面记录的也是边的编号 vector<int> h[N]; //记录每个点的出边所对应的编号 int dfn[N], low[N], tot, cnt; edge bridge[M]; void add(int a, int b) { e.push_back({a, b}); h[a].push_back(e.size() - 1); } void tarjan(int x, int in_edg) { //in_edg 表示从上一个结点*到x进行tarjan深度遍历所经过边的在e中的下标(边的编号),相当于x的入边(*的出边) dfn[x] = low[x] = ++tot; for(int i = 0; i < h[x].size(); i++) { int j = h[x][i], y = e[j].y; //j是x对应的每一条出边的编号 if(!dfn[y]) { tarjan(y, j); // 和上面的含义一致:x -> y 的出边j,相当于y的入边j low[x] = min(low[x], low[y]); if(low[y] > dfn[x]) { // 注意这里不能用等号,因为对于判断割边我们是对边进行单向处理,虽然是无向图,但是我们规定不能走回边,low[y] > dfn[x] 的含义是:不经过[x, y]这条边x的子节点能够回溯到更早的时间戳小于dfn[x],则说明y不能通过其他边到达x或者比x更早的时间戳,所以[x, y]这条边是一条割边(桥) bridge[cnt++] = {x, y}; } } else if(j != (in_edg ^ 1)) { low[x] = min(low[x], dfn[y]); } } } int main() { return 0; }
例题小试:P1656 炸铁路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
这个题是一个tarjan割边的模板题差不多,只需要将存出的割边进行一个简单的排序,这里我用一个优先队列进行维护,用pair来存储边的好处就是不需要重写比较符,pair有排序规则
/*
输入:
6 6 1 2 2 3 2 4 3 5 4 5 5 6
输出:
1 2 5 6
*/
代码实现:
#include<bits/stdc++.h> using namespace std; const int N = 200, M = 5e3 + 10; typedef pair<int, int> PII; //用pair进行存边 int n, m; vector<PII> e; vector<int> h[N]; int dfn[N], low[N], tot; PII bridge[M]; int cnt; void add(int a, int b) { //这里需要对边进行编号,是因为要考虑入边和回边的因素 e.push_back({a, b}); h[a].push_back(e.size() - 1); } void tarjan(int x, int in_idx) { //in_idx代表是入边编号 dfn[x] = low[x] = tot++; for(int i = 0; i < h[x].size(); i++) { int j = h[x][i], v = e[j].second; if(!dfn[v]) { tarjan(v, j); low[x] = min(low[x], low[v]); if(low[v] > dfn[x]) { cnt++; bridge[cnt] = {x, v}; } } else if(j != (in_idx ^ 1)) { // 0, 1; 2, 3; ... 对应一条重边 //这里用异或是因为0^1 == 1(1^1 == 0), 2^1 = 3(3^1 == 2) //刚开始对于一条无向边拆成两条边,入边和出边,从0进行编号,由此0对应1,2对应3,所以这里可以用异或1来进行判断是不是对应回边 low[x] = min(low[x], dfn[v]); } } } int main() { cin >> n >> m; for(int i = 1; i <= m; i++) { int a, b; cin >> a >> b; add(a, b); add(b, a); } for(int i = 1; i <= n; i++) { if(!dfn[i]) tarjan(i, 0); } priority_queue<PII, vector<PII>, greater<PII>> q; for(int i = 1; i <= cnt; i++) { int u = bridge[i].first, v = bridge[i].second; // cout << u << ' ' << v << '\n'; if(u > v) { int t = u; u = v; v = t; } q.push({u, v}); // cout << "change: " << u << ' ' << v << '\n'; } while(q.size()) { auto[x, y] = q.top(); q.pop(); cout << x << ' ' << y << '\n'; } return 0; }
总结一下关于Tarjan的三种常用地方:
一:强联通分量
二:割点
三:割边