Tarjan(年早失修 漏洞百出)

Tarjan是谁

Tarjan's SCCs(有向图强连通分量)algorithm

给定⼀个有向图 G,若存在 rtV,满⾜从 rt 出发能到达 V 中的所有的点,则称 G 是⼀个源点为 rt 流图

rt 出发做 DFS

符号表

  • fa[x]x 节点的父亲节点

  • anc[x]x 节点的祖先点集

  • son[x]:搜索树中 x 节点的儿子节点集

  • e[x]{y | (xy)E}

  • dfn[x]x 节点的时间戳

  • sbt[x]x 节点为根的子树点集

  • low[x]x 节点的追溯值

G 中的每条有向边 xy 必然是以下四种之⼀:

  • 枝:x=fa[y]

  • 前:x(anc[y]fa[y])

  • 后:y(anc[x]fa[x])

  • 横:xanc[y]  yanc[x] 此时一定满足 dfn[x]>dfn[y]

节点上的数字为时间戳:

分析

我们在搜索树上分析,发现“前”边没有什么用处,因为搜索树上本来就存在从 xy 的路径。“后”边非常有用,它可以和搜索树上从 xy 的路径⼀起构成环。“横”边要看情况,如果从 y 出发能找到⼀条路径回到 x 的祖先节点,那么 xy 就是有用的。

low[x] 定义为满⾜以下条件的节点的最小时间戳:

  • 该点在栈中。

  • 存在⼀条从 sbt[x] 出发的有向边,以该点为终点。

tarjan(x) 主体

  1. low[x]=dfn[x]=++dfn_time

  2. for(y:e[x]){tarjan(y) check_min(low[x],low[y])!vis[y]check_min(low[x],dfn[y])yanc[x]do nothingotherwise

  3. if(low[x]=dfn[x]) pop stack until x is poppeda SCC

注意标红和标蓝的不能改,不能错!!!

Code

P1407 [国家集训队]稳定婚姻

vector<int> e[N];
int dfn[N];//时间戳
int low[N];//追溯值
int tim=0;//时间戳计数器
int col[N];//所属 SCC (为其中一个点的 id)
int st[N];//stack
int tot=0;//stack_top
bool in[N];//是(1)否(0)在栈中
void tar(int x){
dfn[x]=low[x]=++tim;//init
st[++tot]=x;//进栈
in[x]=1;
for(int i:e[x]){
if(!dfn[i]){//萌新
tar(i);//递归
ckmn(low[x],low[i]);
}else if(in[i]){//祖先
ckmn(low[x],dfn[i]);
}
}
if(low[x]==dfn[x]){//导出 SCC
do{
col[st[tot]]=x;
//着上 x 的颜色,以后你就是 x 的人了
in[st[tot]]=0;
}while(st[tot--]!=x);//pop until x popped
}
}

Tarjan's BCCs(无向图双连通分量)algorithm

与 SCC 类似,所以符号沿用

注意此时 low[x] 的定义改变,且图 G 中不再存在意义上的“横”边、“前”边。

low[x] 定义为满⾜以下条件之一的节点的最小时间戳:

  • 该点在 sbt[x] 中。

  • 存在⼀条从该点出发至 sbt[x] 中任一点的非树边

剩下的分析和算法就一样了~

桥⼀定是搜索树上的边。

xfa[x]dfn[fa[x]]<low[x]

Code by FuZhenTao

const int SIZE=100010;
int head[SIZE],ver[SIZE*2],nxt[SIZE*2];
int dfn[SIZE],low[SIZE],n,m,tot,num;
bool bridge[SIZE*2];
void add(int x,int y){
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void tarjan(int x,int in_edge){
dfn[x]=low[x]=++num;
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(!dfn[y]){
tarjan(y,i);
low[x]=min(low[x],low[y]);
if(low[y]>dfn[x]) bridge[i]=bridge[i^1]=true;
}
else if(i!=(in_edge^1)){
low[x]=min(low[x],dfn[y]);
}
}
}
int main(){
cin>>n>>m;
tot=1;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
add(x,y);
add(y,x);
}
for(int i=1;i<=n;i++){
if(!dfn[i]) tarjan(i,0);
}
for(int i=2;i<tot;i+=2){
if(bridge[i]) cout<<ver[i^1]<<" "<<ver[i]<<endl;
}
}

割点

x{| son[x] |>1x=rtyson[x] , dfn[x]low[y]xrt

Code by FuZhenTao

void tarjan(int x){
dfn[x]=low[x]=++num;
int flag=0;
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x]){
flag++;
if(x!=root||flag>1) cut[x]=true;
}
}
else low[x]=min(low[x],dfn[y]);
}
}

e-DCC(边双连通分量)

G 是无向连通图。

GeDCCκ(G)2

只需要求出无向图中所有的桥,把桥都删除之后,图会分成若干个连通块,每个连通块就是⼀个"边双连通分量"

Code by FuZhenTao

int c[SIZE],dcc;
void dfs(int x){
c[x]=dcc;
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(c[y]||bridge[i]) continue;
dfs(y);
}
}
for(int i=1;i<=n;i++){
if(!c[i]){
++dcc;
dfs(i);
}
}

v-DCC(点双连通分量)

GvDCCκ(G)2n(G)2

v-DCC 的求法炒鸡麻烦,鸽了。

由于 Tarjan O(n) 求 LCA 好像并不是 Tarjan 的算法,而且倍增 O(nlogn) 好用并好写,所以就不再论述了。

posted @   ShaoJia  阅读(80)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
点击右上角即可分享
微信分享提示