变量释义
t
a
r
j
a
n
tarjan
tarjan算法的两个核心数组:
d
f
n
dfn
dfn数组和
l
o
w
low
low数组。
dfn数组:节点在
d
f
s
dfs
dfs树上的序号(相当于一个时间戳的作用)。
low数组:节点在
d
f
s
dfs
dfs树上的子树(不完全是子树,要扣掉已经分配到强连通分量的点)中,所能返回的点的
d
f
n
dfn
dfn的最小值(相当于找强连通分量的根)。
l
o
w
low
low数组比较难懂,可以意会一下,强连通分量的关键是互相可达,
d
f
s
dfs
dfs的作用相当于就是祖先到子孙的可达,而
l
o
w
low
low数组就是让子孙尽可能地与更上层的祖先可达,从而达到互相可达的作用。
因此可以得出这样一个性质:强连通分量的根满足
d
f
n
[
x
]
=
l
o
w
[
x
]
dfn[x]=low[x]
dfn[x]=low[x]。
注:强连通分量的根指强连通分量中dfn最小的点。
核心流程
tarjan算法的核心流程包括三个部分:
d
f
s
dfs
dfs确定
d
f
n
dfn
dfn数组,取子树中(尚未分配到强连通分量)low值最小的点,确定每个强连通分量的根与其余点。
d
f
s
dfs
dfs不必多说
取low值最小如何做?
d
f
s
dfs
dfs的过程中顺便更新为儿子的
l
o
w
low
low的最小值(有点树形dp的味道)。
如何判断是否分配到强连通分量?比较快捷的方法就是用一个SCC记录点所属的强连通分量的序号。
但是为了下一步确定强连通分量中的所有点,这里用栈来存搜索到了但未分配的点。
最后一步,确定SCC,当子树搜索完但该点low值没有变化时,该点即为强连通分量的根,弹出所有栈中的元素直到它本身,作为强连通分量(类比一下
d
f
s
dfs
dfs确定该点
d
f
s
dfs
dfs子树中的点的流程)。
代码如下:
变量声明:
vector <int> v[1005];//邻接表
stack <int> s;
int in_stack[1005]; //判断是否在栈中
int visit[1005]; //判断是否访问过
int dfn[1005]; //dfn指代i在dfs序列的位置
int low[1005]; //low数组指代该连通分量的根节点位置
int count1,count2; //count1用于dfn,low的编号,count2用于强连通分量的编号
int scc[1005]; //编号为i的点所在的强连通分量的序号
int scc1[1005]; //编号为i的强连通分量的节点数目
vector <int> v1[1005]; //将每个连通分量的点都放到向量中
核心函数:
void tarjan(int now){
dfn[now]=low[now]=++count1;
in_stack[now]=1;
visit[now]=1;
s.push(now);
for(int i=0;i<v[now].size();i++){
if(!visit[v[now][i]]){ //v未访问,访问v,low值取较小的
tarjan(v[now][i]);
low[now]=min(low[now],low[v[now][i]]);
}
else if(in_stack[v[now][i]]){ //访问但未出栈,low值取较小的
low[now]=min(low[now],low[v[now][i]]);
}
}
if(low[now]==dfn[now]){ //now为连通分量根节点,出栈直至now,把它们都放到同一个连通分量中
while(s.top()!=now){
scc[s.top()]=count2;
scc1[count2]++;
v1[count2].push_back(s.top());
in_stack[s.top()]=0;
s.pop();
}
scc[s.top()]=count2;
scc1[count2]++;
v1[count2].push_back(s.top());
in_stack[s.top()]=0;
s.pop();
count2++;
}
return;
}
主函数内:
for(int i=1;i<=n;i++){
if(!visit[i]){
tarjan(i);
}
}
典型应用
t
a
r
j
a
n
tarjan
tarjan算法一般配合缩点来使用,缩点后容易观察到,有向图成为有向无环图,可以进行dp,拓扑排序等操作。
推荐几个例题,方法都差不多。
洛谷P2863 模板
洛谷P3387 配dp使用
洛谷P2746 有一定思维难度