Tarjan三

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的三种常用地方:

一:强联通分量

二:割点

三:割边

Tarjan算法是一种用于解决最近公共祖先(LCA)问题的离线算法。离线算法指的是在读取所有查询之后一次性计算所有查询的答案,而不是每读取一个查询就计算一次。\[1\] 在Tarjan算法中,需要使用并查集来实现。并查集是一种数据结构,用于维护元素之间的集合关系。下面是一个并查集的模板代码: ```cpp int fa\[100000\]; void reset(){ for (int i=1;i<=100000;i++){ fa\[i\]=i; } } int getfa(int x){ return fa\[x\]==x?x:getfa(fa\[x\]); } void merge(int x,int y){ fa\[getfa(y)\]=getfa(x); } ``` 在Tarjan算法的伪代码中,首先标记当前节点为已访问状态。然后遍历当前节点的子节点,递归调用Tarjan函数并合并子节点。接下来,遍历与当前节点有查询关系的节点,如果该节点已经访问过,则输出当前节点和该节点的LCA(通过并查集的查找函数getfa获取)。\[3\] 以上是关于Tarjan算法求解LCA的相关内容。 #### 引用[.reference_title] - *1* [Tarjan 算法解决 LCA 问题](https://blog.csdn.net/chengqiuming/article/details/126878817)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [详解使用 Tarjan 求 LCA 问题(图解)](https://blog.csdn.net/weixin_34315485/article/details/93801193)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幻听嵩的留香

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值