算法 {DFS,DFS树,DFS的DAG图}
DFS
定义
一个函数内部 会继续调用自己, 则该函数是DFS;
应用
@DELI;
#DFS(cur) -> DFS(nex)
当cur==nex
#
我们知道, 此时会导致死循环, 所以必须要避免 即DFS(nex)
不可以执行, 然而 我们当前的DFS(cur)
他的返回值 又需要DFS(nex)
, 这怎么办呢? 其实这就对应了一个数学等式 &x: DFS(cur)返回值
, 比如此时此时得到x = A + K*x
({A,K}
是我们在dfS(cur)
可以求出来的值), 然后转换就得到x = A / (1-K)
; 因此 你不用执行dfs(nex)
只要得到A,K
就得到了答案;
例题@LINK: https://atcoder.jp/contests/abc350/tasks/abc350_e
;
{DFS树,DFS的DAG图}
定义
Time: 0 // 时间戳
Tree: DFS树(是个有向树)
Dag: DFS的DAG图
Dfs( st){ // st表示函数参数
++ Time;
一旦执行Dfs( nex):
. Tree.add_edge( (st,当前函数时间戳), (nex,递归函数时间戳));
. Dag.add_edge( st, nex);
}
即, 一次函数调用 和st
不是一一对应的, 因为同一个st 他可以多次调用, 所以 要添加时间戳 这样就不重复了;
Dag
可以通过: 将Tree
的所有节点信息里的时间戳给去除掉, 然后将相同的节点 合并成一个;
@DELI;
#举个例子#
#样例DFS函数#
A: [1,1,1]
void dfs( cur, ma){
if(cur==3){ return;}
dfs(cur+1, ma); // 不选
dfs(cur+1, max(ma,A[cur])); // 选
}
dfs( 0, -1);
#DFS树#
(0,-1,0)
(1,-1,1) (1,1,8)
(2,-1,2) (2,1,5) (2,1,9) (2,1,12)
(3,-1,3) (3,1,4) (3,1,6) (3,1,7) (3,1,10) (3,1,11) (3,1,13) (3,1,14)
#DFS的DAG#
(0,-1)
/ \
(1,-1) (1,1)
| \ |
(2,-1) (2,1)
| \ /
(3,-1) (3,1)
错误
错误说法: 对于非记忆化的DFS, 相同的状态(即函数参数) 一定不会多次访问;
这是错误的, 否则我们为什么叫做DAG图 而不叫Tree图呢? 即便重复调用(没有记忆化), 不一定就会死循环; @LINK: @TODO
; 可以访问重复节点, 但不可以访问父节点, 否则才会死循环;
因此 千万不要以为: 有多少个不同的函数参数, 就有多少次函数调用; DFS的耗时 取决于DFS树的规模 (而不是DAG图的规模) 也就是最大时间戳;
性質
DFS樹滿足: 祖先節點的時間戳 一定小於 其子節點的時間戳;
因此DFS樹 反映的 就是當時進行Dfs()
的順序;
@DELI;
即對於一個連通無向圖, 他的DFS樹 一定是一個樹(即是弱連通的); 但是對於有向圖 他的DFS樹 可能是一個森林;
@DELI;
DFS樹 一定是唯一確定的, 因为DFS过程是确定的;
@DELI;
這個圖非常非常重要, 對有向圖/無向圖進行DFS 從深層次來說 實際上 你的算法 就是在以時間戳遞增的次序 依次的遍歷這棵樹的每個節點 (按照時間戳次序0->1->2->3...
);
比如Tarjan_{SCC,BCC,CutPoint}
算法 從DFS的角度 其實就是在遍歷這個DFS樹; 因此單純講這個樹 由於樹的性質 你可以維護很多信息 (比如以某點為樹根的子樹的大小);
例題
@TODO B樹
笔记
G: 有向圖 (如果無向圖 則將其無向邊轉換為2條有向邊), 這個圖 對應代碼層面上的那個`___Graph` 即他的臨界邊是有次序優先級的;
DFS_G: 初始為空, 表示一顆有向樹;
vector<int> DfsId( G.PointsCount, -1); // 時間戳
DfsIdCounter = 0;
for( i, 0, G.PointsCount - 1){
if( DfsId[i] == -1){ Dfs(i);}
}
void Dfs( int cur){
DfsId[ cur] = DfsIdCounter ++;
for( nex : G裡cur的鄰接點){
if( DfsId[nex] == -1){
DFS_G.Add_Edge( cur, nex);
Dfs( nex);
}
}
}
最終, {DFS_G, DfsId}
這個聯合體 表示G的DFS樹(DFS森林);
@DELI;
樣例
對於有向圖0->2, 1->2
, 他對應的DFS樹為0(0)->2(1), 1(2)
(1(2): 表示1號節點的DfsId時間戳為2
), 實際上他是一個森林;
對於無線圖0-2, 1-2
, 他對應的DFS樹為0(0)->2(1)->1(2)
;
有向圖的DFS圖
定義
有向圖G, 則{G, DfsId}
這個聯合體(DfsId
為G的DFS樹的時間戳) 為G的DFS圖;
.
換句話說, G本身 節點上加上一個時間戳 就是他的DFS圖;
@DELI;
筆記
定义
void dfs( cur){
vis[ cur] = true;
for( nex : $(adjacency-points of `cur`)){
//>< @Mark_0
if( vis[ nex]){
//>< @Mark_1
continue;
}
//--
dfs( nex);
//>< @Mark_2
}
}
When you call dfs( x)
, you would get a Directed-Graph
G
G
G (i.e., the function dfs
is actually traversing on
G
G
G);
@DELI;
Property-0
Every point
a
∈
G
a \in G
a∈G, means there is exactly one call of dfs( a)
;
Every edge
(
a
→
b
)
∈
G
(a\to b) \in G
(a→b)∈G, means the @Mark_0
(i.e.,
c
u
r
=
a
,
n
e
x
=
b
cur = a, nex = b
cur=a,nex=b);
@Delimiter
Property-1 (REDIRECT-ID-0)
There is a notion of DFS-Order for every point in
G
G
G, that is the timestamp for dfs( a)
;
If you wanna record some information for every point
a
a
a (e.g., let it be
d
p
[
a
]
dp[a]
dp[a]), then the information (
d
p
[
a
]
dp[a]
dp[a]) must satisfies some requirements:
0
d
p
[
a
]
dp[a]
dp[a] is a Tree that rooted by
a
a
a;
1
In this Tree,
(
d
e
p
t
h
[
x
]
>
d
e
p
t
h
[
y
]
)
⟹
(
d
f
s
o
r
d
e
r
[
x
]
>
d
f
s
o
r
d
e
r
[
y
]
)
∀
x
,
y
∈
T
r
e
e
(depth[x] > depth[y]) \implies (dfsorder[x] > dfsorder[y]) \quad \forall x,y \in Tree
(depth[x]>depth[y])⟹(dfsorder[x]>dfsorder[y])∀x,y∈Tree;
The calculation for
d
p
[
a
]
dp[a]
dp[a] can only placed in @Mark_2
;
.
At @Mark_1
, the information of
n
e
x
nex
nex maybe not yet accomplished (e.g.,
n
e
x
nex
nex has many adjacency-points, but at dfs(cur)
,
n
e
x
nex
nex has visited just one adjacency-points), so this proved the above Requirement-1
;
--
For example, we know that the purpose for such information
d
p
[
]
dp[]
dp[], is to get a Sub-Graph (Tree) of the DFS-Graph, So given a Undirected-Graph suppose the Answer is a Line
a
−
b
−
c
a-b-c
a−b−c with length
3
3
3;
.
A Line satisfies the Requirement-0
(Tree);
.
Let
r
r
r be the point with the Smallest dfs-order
among abc
(i.e., the root is
r
r
r), then you need make sure this tree always satisfies the Requirement-1
;
.
.
If the dfs-order
is
b
>
a
,
b
>
c
b > a, b > c
b>a,b>c, then
r
=
a
/
b
r = a/b
r=a/b which not conform with the Requirement-1
;
.
.
e.g., for the undirected-graph
0
−
1
−
2
−
3
,
3
−
0
0-1-2-3, 3-0
0−1−2−3,3−0 (the number is also dfs-order
),
d
p
[
0
]
dp[0]
dp[0] would not get the answer
2
−
3
−
0
2-3-0
2−3−0;
Overall, given a Undirected-Graph, calculate a Line whose length is
k
k
k (i.e.,
a
1
−
a
2
−
.
.
.
−
a
k
a_1 - a_2 - ... - a_k
a1−a2−...−ak) and the answer is the value of the line (the definition of value is arbitrary);
.
If you use
d
p
[
x
]
[
j
]
dp[x][j]
dp[x][j] to denote a line with length
j
j
j (
x
−
y
−
z
−
.
.
.
x - y - z -...
x−y−z−..., and
d
f
s
o
r
d
e
r
[
x
]
<
d
f
s
o
r
d
e
r
[
y
]
<
d
f
s
o
r
d
e
r
[
z
]
<
.
.
.
dfsorder[x] < dfsorder[y] < dfsorder[z] < ...
dfsorder[x]<dfsorder[y]<dfsorder[z]<...), this method is Infeasible;
无向图的DSF图
定义
無向圖G, 令其DFS樹為{T,DfsId}
, 令T1
為T的反圖(即T中的a->b
對應T1裡的b->a
), 則{G - T1, DfsId}
這個聯合體為G的DFS圖;
.
比如G: {a-b, b-c, c-a}
, 其DFS樹為a(0)->b(1)->c(2)
, 則其DFS圖為a(0)->b(1)->c(2), c-a
;