当前位置: 首页 > news >正文

Tarjan 学习笔记

一、预备知识

1. 强连通图

有向图 \(G\) 强连通是指,\(G\) 中任意两个结点连通。
强连通分量(Strongly Connected Components,SCC)的定义是:极大的强连通子图。

2. DFS 生成树


有向图的 DFS 生成树主要有 4 种边(不一定全部出现):

  • 树边(tree edge):示意图中以黑色边表示,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边。
  • 反祖边(back edge):示意图中以红色边表示(即 \(7→1\)),也被叫做回边,即指向祖先结点的边。
  • 横叉边(cross edge):示意图中以蓝色边表示(即 \(9→7\)),它主要是在搜索的时候遇到了一个已经访问过的结点,但是这个结点 并不是 当前结点的祖先。
  • 前向边(forward edge):示意图中以绿色边表示(即 \(3→6\)),它是在搜索的时候遇到子树中的结点的时候形成的。

3. DFS 生成树和强连通分量的关系

如果结点 \(u\) 是某个强连通分量在搜索树中遇到的第一个结点,那么这个强连通分量的其余结点肯定是在搜索树中以 \(u\) 为根的子树中。结点 \(u\) 被称为这个强连通分量的根。

二、Tarjan 算法

1. 概述

Robert E. Tarjan(罗伯特·塔扬,1948~),生于美国加州波莫纳,计算机科学家。
Tarjan 发明了很多算法和数据结构。不少他发明的算法都以他的名字命名,以至于有时会让人混淆几种不同的算法。比如求各种连通分量的 Tarjan 算法,求 LCA(Lowest Common Ancestor,最近公共祖先)的 Tarjan 算法。并查集、Splay、Toptree 也是 Tarjan 发明的。
大牛啊。

2. 思路

Tarjan 算法基于对图进行深度优先搜索。
我们视每个连通分量为搜索树中的一棵子树,在搜索过程中,维护一个栈,每次把搜索树中尚未处理的节点加入栈中。
对于每一个 \(u\),维护这么几个变量:

  • \(dfn_u\):深度优先搜索时 \(u\) 被搜索的次数,即 dfs 序中对应的编号;
  • \(low_u\):在以 \(u\) 为根的子树中最早能遍历到的节点。

可以证明一个有趣的结论:在从根节点到一个叶子节点的路径上,\(dfn\) 严格单增,\(low\) 严格非降。


按照深度优先搜索算法搜索的次序对图中所有的结点进行搜索,维护每个结点的 \(dfn\)\(low\) 变量,且让搜索到的结点入栈。每当找到一个强连通元素,就按照该元素包含结点数目让栈中元素出栈。
对于节点 \(u\) 和节点 \(v\)\(v\) 不是 \(u\) 的父亲节点:

  1. \(v\) 未被访问:继续访问 \(v\),并用 \(low_v\) 来更新 \(low_u\)
  2. \(v\) 被访问过,已在栈中:用 \(dfs_v\) 跟新 \(low_u\)
  3. \(v\) 被访问过,不在栈中:无需处理。

把代码转换为伪代码:

TARJAN_SEARCH(int u)vis[u]=truelow[u]=dfn[u]=++dfncntpush u to the stackfor each (u,v) then doif v hasn't been searched thenTARJAN_SEARCH(v) // 搜索low[u]=min(low[u],low[v]) // 回溯else if v has been in the stack thenlow[u]=min(low[u],dfn[v])

这里有一张动态演示图


那么,对于第一个被强连通分量访问到的节点而言,存在 \(dfs_u=low_u\),而且一个强连通分量中尤其只有一个这样的点。

三、例题讲解

说是讲解,其实挺惭愧,被板子题肘击了。

1. 题面

P3387 【模板】缩点

题目描述

给定一个 \(n\) 个点 \(m\) 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。

允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

输入格式

第一行两个正整数 \(n,m\)

第二行 \(n\) 个整数,其中第 \(i\) 个数 \(a_i\) 表示点 \(i\) 的点权。

第三至 \(m+2\) 行,每行两个整数 \(u,v\),表示一条 \(u\rightarrow v\) 的有向边。

输出格式

共一行,最大的点权之和。

输入输出样例 #1

输入 #1
2 2
1 1
1 2
2 1
输出 #1
2

说明/提示

对于 \(100\%\) 的数据,\(1\le n \le 10^4\)\(1\le m \le 10^5\)\(0\le a_i\le 10^3\)

  • 2024-11-1 添加了 hack 数据;

2. 思路

大概就是用 Tarjan 将强连通图缩成一个点,毕竟这些点互相可达,就像你拿到某国的护照就可以去这个国家任意一个省份是一个道理,没必要再看点了。这样会剩下不少时间。
然后关于求最大点值,这里采用拓扑排序,套用 DP,就能做出来了。
这个主包讲的不错:更好的讲解

3. 代码

写的很烂,批判性参考。

#include <iostream>
#include <cstdio>
#include <vector>
#include <stack>
#include <queue>
#define int long long 
using namespace std;
const int MAXN=1e5+5;
bool check(int);
void tarjan(int);
void topsort();
vector<int> g[MAXN],G[MAXN];
int indeed[MAXN],belong[MAXN];
int a[MAXN],u[MAXN],v[MAXN];
int f[MAXN];
int n,m,ans;
signed main(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=1;i<=m;i++){cin>>u[i]>>v[i];g[u[i]].push_back(v[i]);}for(int i=1;i<=n;i++){if(!check(i)){tarjan(i);}}for(int i=1;i<=m;i++){int x=belong[u[i]],y=belong[v[i]];if(x!=y){// 顶点不同G[x].push_back(y);indeed[y]++;}}topsort();for(int i=1;i<=n;i++){ans=max(ans,f[i]);}cout<<ans;return 0;
}
stack<int> s;
bool book[MAXN];
int low[MAXN],dfn[MAXN];
int scc[MAXN];// 节点在强连通分量中的编号
int len[MAXN];// 这个强连通分量的大小 
int dfncnt,size;
bool check(int u){return dfn[u];
} 
void tarjan(int u){low[u]=dfn[u]=++dfncnt;// 赋 dfs 序编号 s.push(u);book[u]=true;// 在栈中for(auto v:g[u]){if(!dfn[v]){// 这个点没有遍历过 tarjan(v);low[u]=min(low[u],low[v]);}else if(book[v]){// 在栈中low[u]=min(low[u],dfn[v]); }}if(dfn[u]==low[u]){// 强连通分量的起始点 ++size;while(true){int t=s.top();s.pop();scc[t]=size,book[t]=false;belong[t]=u;// 链接点 len[size]++;if(t==u){break;}a[u]+=a[t];// 缩点 }}
}
queue<int> q;
void topsort(){// 拓扑排序for(int i=1;i<=n;i++){if(i==belong[i]&&!indeed[i]){q.push(i);f[i]=a[i];}}while(!q.empty()){int u=q.front();q.pop();for(auto v:G[u]){f[v]=max(f[v],f[u]+a[v]);indeed[v]--;if(!indeed[v]){q.push(v);} }}
}
http://www.hskmm.com/?act=detail&tid=10249

相关文章:

  • 使用JavaScript和CSS创建动态高亮导航栏
  • Gridspech 全通关
  • 1967
  • 20253320蒋丰任
  • .
  • 又有两位智驾大牛联手入局具身智能机器人赛道创业,已完成数亿元融资!
  • 纯国产GPU性能对比,谁才是国产算力之王?
  • 地平线明年发布并争取量产舱驾一体芯片;比亚迪补强智舱团队,斑马智行原 CTO 加入
  • 英伟达入股英特尔,当竞争对手便成协作者,真正受益的......
  • ODT/珂朵莉树 入门
  • 在AI技术快速实现功能的时代,挖掘新需求成为关键突破点——某知名游戏资源分析工具需求洞察
  • 蜜罐
  • 【光照】[漫反射]UnityURP兰伯特有光照衰减吗?
  • prenotami.esteri.it 意大利签证预约error
  • reLeetCode 热题 100- 15. 三数之和 - MKT
  • XXL-TOOL v2.1.0 发布 | Java工具类库
  • Python-Pathlib库
  • 反省
  • [Nacos/Docker/MCP] Nacos 3.x : 为 AI MCP 而生
  • 牛客周赛 Round 108 CDEF题解
  • Redis的使用问题
  • AIGC拾遗:Flash Attention
  • 深度好文-风雨飘摇信竞路
  • Python-CSV库
  • C++小白修仙记_LeetCode刷题_位运算
  • C++小白修仙记_LeetCode刷题_双指针
  • 前路漫漫亦灿灿 往事堪堪亦澜澜
  • 使用uv和pycharm搭建python开发环境
  • lc1032-字符流
  • C++小白修仙记_LeetCode刷题_哈希表