搜索树:
给定有向图 G = ( V , E ) G=(V,E) G=(V,E),若存在 r ∈ V r\in V r∈V,满足从 r r r出发能够到达 V V V中所有的点,则称 G G G是一个"流图",记为 ( G , r ) (G,r) (G,r),其中 r r r称为流图的源点。在一个流图 ( G , r ) (G,r) (G,r)上从 r r r出发进行深度优先遍历,每个点只访问一次。所有发生递归的边 ( x , y ) (x,y) (x,y)(也即从 x x x到 y y y是对 y y y的第一次访问)构成一棵以 r r r为根的树,我们把它称为流图 ( G , r ) (G,r) (G,r)的搜索树。
时间戳:
d f s dfs dfs中每个节点第一次被访问的时间顺序。也就是 d f s dfs dfs序。
流图的有向边:
每条有向边
(
x
,
y
)
(x,y)
(x,y)必然是以下四种之一:
1.树枝边,指搜索树中的边,即
x
x
x是
y
y
y的父节点。
2.前向边,指搜索树中
x
x
x是
y
y
y的祖先节点。
3.后向边,指搜索树中
y
y
y是
x
x
x的祖先节点。
4.横叉边,指除了以上三种情况之外的边,它一定满足
d
f
n
[
y
]
<
d
f
n
[
x
]
dfn[y]<dfn[x]
dfn[y]<dfn[x]。
有向图的强连通分量:
给定一张有向图。若对于图中任意两个节点
x
,
y
x,y
x,y既存在从
x
x
x到
y
y
y的路径,也存在从
y
y
y到
x
x
x的路径,则称该有向图是"强连通图"。
有向图的极大连通子图被称为"强连通分量",简记为
S
C
C
SCC
SCC。
一个环一定是强连通图,因此
t
a
r
j
a
n
tarjan
tarjan算法的基本思路就是对于每一个点,尽量找到与它一起能构成环的所有节点。
追溯值:
后向边
(
x
,
y
)
(x,y)
(x,y)非常有用,因为它可以和搜索树上从
y
y
y到
x
x
x的路径一起构成环。横叉边
(
x
,
y
)
(x,y)
(x,y)视情况而定,如果从
y
y
y出发能找到一条路径回到
x
x
x的祖先节点,那么
(
x
,
y
)
(x,y)
(x,y)就是有用的。为了找到环,引入了追溯值的概念。
设
s
u
b
t
r
e
e
(
x
)
subtree(x)
subtree(x)表示流图的搜索树中以
x
x
x为根的子树。
x
x
x的追溯值
l
o
w
[
x
]
low[x]
low[x]定义为满足以下条件的节点的最小时间戳:
1.改点在栈中。
2.存在一条从
s
u
b
t
r
e
e
(
x
)
subtree(x)
subtree(x)出发的有向边,以该点为终点。
t
a
r
j
a
n
tarjan
tarjan按照以下步骤计算追溯值:
1.当节点
x
x
x第一次被访问时,把
x
x
x入栈,初始化
l
o
w
[
x
]
=
d
f
n
[
x
]
low[x]=dfn[x]
low[x]=dfn[x]。
2.扫描从
x
x
x出发的每条边
(
x
,
y
)
(x,y)
(x,y)。
(1)若
y
y
y没被访问过,说明
(
x
,
y
)
(x,y)
(x,y)是树枝边,递归访问
y
y
y,从
y
y
y回溯之后,令
l
o
w
[
x
]
=
m
i
n
(
l
o
w
[
x
]
,
l
o
w
[
y
]
)
low[x]=min(low[x],low[y])
low[x]=min(low[x],low[y])。
(2)若
y
y
y被访问过并且
y
y
y在栈中,则令
l
o
w
[
x
]
=
m
i
n
(
l
o
w
[
x
]
,
d
f
n
[
y
]
)
low[x]=min(low[x],dfn[y])
low[x]=min(low[x],dfn[y])。
3.从
x
x
x回溯之前,判断是否有
l
o
w
[
x
]
=
d
f
n
[
x
]
low[x]=dfn[x]
low[x]=dfn[x]。若成立,则不断从栈中弹出节点,直至
x
x
x出栈。
强连通分量的判定法则:
从
x
x
x回溯之前,若有
l
o
w
[
x
]
=
d
f
n
[
x
]
low[x]=dfn[x]
low[x]=dfn[x]成立,则栈中从
x
x
x到栈顶的所有节点构成一个强连通分量。因为
l
o
w
[
x
]
=
d
f
n
[
x
]
low[x]=dfn[x]
low[x]=dfn[x]说明
s
u
b
t
r
e
e
(
x
)
subtree(x)
subtree(x)中的节点不能与栈中其他节点一起构成环。另外因为横叉边的终点时间戳必然小于起点时间戳,所以
s
u
b
t
r
e
e
(
x
)
subtree(x)
subtree(x)中的节点也不可能直接到达尚未访问的节点。因此栈中从
x
x
x到栈顶的所有节点不可能与其他节点一起构成环。
c
o
d
e
:
code:
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=1e5+5;
const int maxm=1e5+5;
struct edge
{
int to,nxt;
}Edge[maxm];
int head[maxn],dfn[maxn],low[maxn],Stack[maxn],ins[maxn],id[maxn];
vector<int> scc[maxn];
int n,m,tot,num,top,cnt;
void addedge(int x,int y)
{
Edge[++tot].to=y,Edge[tot].nxt=head[x],head[x]=tot;
}
void tarjan(int x)
{
dfn[x]=low[x]=++num;
Stack[++top]=x,ins[x]=1;
int y;
for(int i=head[x];i;i=Edge[i].nxt)
{
y=Edge[i].to;
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(ins[y])
low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
++cnt;
do
{
y=Stack[top--],ins[y]=0;
id[y]=cnt,scc[cnt].push_back(y);
}while(x!=y);
}
}
int main()
{
scanf("%d%d",&n,&m);
int x,y;
for(int i=0;i<m;i++)
{
scanf("%d%d",&x,&y);
addedge(x,y);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
return 0;
}
缩点:
把每一个
S
C
C
SCC
SCC缩成一个点。
c
o
d
e
:
code:
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=1e5+5;
const int maxm=1e5+5;
struct edge
{
int to,nxt;
}Edge[maxm];
int head_sd[maxn];
edge sd[maxm];
int head[maxn],dfn[maxn],low[maxn],Stack[maxn],ins[maxn],id[maxn];
vector<int> scc[maxn];
int n,m,tot,num,top,cnt,tol;
void addedge(int x,int y)
{
Edge[++tot].to=y,Edge[tot].nxt=head[x],head[x]=tot;
}
void addedge_sd(int x,int y)
{
sd[++tol].to=y,sd[tol].nxt=head_sd[x],head_sd[x]=tol;
}
void tarjan(int x)
{
dfn[x]=low[x]=++num;
Stack[++top]=x,ins[x]=1;
int y;
for(int i=head[x];i;i=Edge[i].nxt)
{
y=Edge[i].to;
if(!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(ins[y])
low[x]=min(low[x],dfn[y]);
}
if(dfn[x]==low[x])
{
++cnt;
do
{
y=Stack[top--],ins[y]=0;
id[y]=cnt,scc[cnt].push_back(y);
}while(x!=y);
}
}
int main()
{
scanf("%d%d",&n,&m);
int x,y;
for(int i=0;i<m;i++)
{
scanf("%d%d",&x,&y);
addedge(x,y);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
for(int i=1;i<=n;i++)
{
for(int j=head[i];j;j=Edge[j].nxt)
{
y=Edge[j].to;
if(id[i]==id[y])
continue;
addedge_sd(id[i],id[y]);
}
}
return 0;
}
结论:
1.缩点后得到的一定是一个有向无环图
(
D
A
G
)
(DAG)
(DAG)。
2.从
D
A
G
DAG
DAG中所有入度为
0
0
0的点出发一定可以遍历整张图。
3.
D
A
G
DAG
DAG中至少存在
1
1
1个出度为
0
0
0的点。
4.当
D
A
G
DAG
DAG中只有
1
1
1个出度为
0
0
0的点时,其他所有点均可到达该点。
5.任意一个有向图,在缩点后的有向无环图中有
p
p
p个零入度点,
q
q
q个零出度点,那么最少添加
m
a
x
(
p
,
q
)
max(p,q)
max(p,q)条边就可以把这个图变成强连通图。