算法 {树Tree}
@LOC: 2
树Tree
定义
#無向树#
无向图
G
=
(
V
,
E
)
G=(V,E)
G=(V,E) 满足{1:[任意两点均可达]; 2:[
∣
V
∣
=
∣
E
∣
+
1
|V| = |E| + 1
∣V∣=∣E∣+1]};
等價表述: 不存在的(割邊(橋))的連通無向圖 為樹;
@DELI;
#有向树#
對於無向樹G 選擇一個根節點R, 對於G中的任意無向邊a-b
如果a的深度 < b的深度
則講該無向邊變成a->b
有向邊;
@DELI;
#子樹#
樹G中, 一個點集S
如果滿足: 對任意a,b \in S
, 樹G上a,b
的簡單路徑上的所有點 都在S內, 則S對應導出子圖 稱之為子樹;
.
比如對於一顆標準二叉樹(x的子節點為2*x, 2*x+1
), 點集{9,4,2,5,1,3}
為子樹;
#以x為根的子樹#
樹G中, 由{x, x的所有子節點}
組成的點集 對應的導出子圖, 稱之為以x為根的子樹;
#树上(简单)路径#
跟简单路径的定义一样, 不经过重复点的路径;
性质
@MARK: @LOC_1
;
#树的所有节点的度数的方案 是全排列#
对于一颗N个点的树, 因为他有N-1
条边 因此这N个点的度数之和 一定等于(N-1)*2
; 我们令Di
数组 表示每个点的度数, 那么一定能够满足: Di >= 1
和 Di之和 == (N-1)*2
(因为树有N-1
条边 每个边贡献两个度数);
那么其实 这个结论 不仅是充分性 也是必要性, 换句话说: 满足Di>=1 && Di之和=(N-1)*2
的Di
数组 他一定可以形成一颗N个点的树;
.
举个例子, N=4
, 对于0-1-2-3
这个树 他的度数为[1,2,2,1]
满足结论; 那么反过来 对于任意的序列 比如[1,1,1,3]
他对应0-3-1, 3-2
;
.
证明的话, 还不会…
@DELI;
对于有根无向树来说,对于一条边a-b
其实你不需要指定 他俩谁是父节点谁是子节点, 因为给定你一个无向树T (此时他里面任意一条边a-b
你不知道他俩谁是父谁是子), 但是只要给T指定一个根R, 即这个T + R
那么此时 所有的边a-b
他其实就变成了有向边 即a,b
谁是父谁是儿子 此时是固定的! 因为R根是固定了;
@DELI;
對於無向無根樹(不是森林), 假如你要計算他的葉子節點的個數, 對於某個點x 他的鄰接點個數為c
, 通常 對葉子節點的定義是: c==1
, 而不是c <= 1
; (這點的區間 就在於 對於一個只有1個點的樹 這一個點 你到底要不要把他定義為葉子, 這需要你自行決定)
@DELI;
#树上任意两点间 一定且只有1条简单路径#
MARK: @LOC_0
;
##推论##: 树上一定有
N
+
(
N
∗
N
−
N
)
2
N+ \frac{(N*N - N)}{2}
N+2(N∗N−N)条简单路径;
.
因为任选两个点可以确定一条简单路径 我们规定(a <= b)
来表示简单路径, 则长度为1的简单路径 即(i,i)
为N个; 长度
>
1
>1
>1的简单路径 由于(a<b) (b>a)
有两个 因此
(
N
2
−
N
)
/
2
(N^2 - N)/2
(N2−N)/2个;
.
换句话说 通过for( i = 0; i < N; ++i) for( j = i; j < N; ++j)
即可以遍历所有的简单路径;
@DELI;
#有向树 一定是DAG;#
例题
@LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=140104027
;
D[N]
满足Di>=1 && Di之和=(N-1)*2
的序列, 一定可以对应到 一棵N个点的树的度数;
@DELI;
@LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=133279832
树上路径中 只含有一个特殊点的路径个数
@DELI;
树上回文路径的个数:
LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=131933499
;
錯誤
無向樹的遍歷 有一個代碼: dfs(cur,fa){ for(nex){ if(nex!=fa){ dfs(nex,cur);}}}
, 這個代碼 非常經典, 過去一直用的都是這個代碼, 但其實 他是有問題的…
假如你的樹 她是有重邊的, 那麼 這個經典代碼 是有大問題的, 一定會超時…
所以 最好的辦法 就是使用通用的 對無向圖的遍歷模板, 即使用一個Vis[]
把fa
參數去掉即可, 每次dfs( cur){ Vis[cur]=1; for(nex){ if(!Vis[nex]){ dfs(nex);}}}
, 這個代碼 是絕對正確的 通用的;
算法
删除树的叶节点
@LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=137605680
;
树的换根DP
@LINK: https://editor.csdn.net/md/?articleId=133280209
;
求任意两点间路径的权值
通过预处理前缀和, a-b
路径 可转换为: a-树根
的路径 加上 b-树根
的路径 减去 2倍的ab的LCA-树根
的路径;
LINK: (https://editor.csdn.net/md/?not_checkout=1&articleId=128426984)-(@LOC_0
);
筆記
#基础概念#
All basic notions in the Undirected-Graph are also adapted in a Tree (cuz a Tree is also a Undirected-Graph);
--
An Unrooted-Tree (无根树) has several equivalent definitions:
+
An Undirected-Connected-Graph with
N
N
N points and
N
−
1
N-1
N−1 edges;
+
An Undirected-Connected-Graph with no Loop;
+
Any two points have one and only one Simple-Path;
+
Every edge is a Cut-Edge (Bridge);
--
An Rooted-Tree (有根树) is an Unrooted-Tree with an additional designating point
r
r
r called Root;
.
One difference to the Unrooted-Tree is that, there is a Hierarchy between any two points;
--
A Forest (森林) is an Undirected-Graph with every Connected-Component be a Tree;
.
A Tree is also a Forest;
--
A Leaf-Node (叶子结点):
.
1
In the Unrooted-Tree, it is a point whose Degree
≤
1
\leq 1
≤1;
.
.
Specially, if
N
=
1
N = 1
N=1, the sole point (whose Degree is
0
0
0) is also a Leaf-Node;
.
2
In the Rooted-Tree, it is a point which has no Child-Node;
--
Simple-Path (简单路径)
.
Any two points
a
,
b
a,b
a,b in a Tree have one and only one Simple-Path
a
−
.
.
.
−
b
a-...-b
a−...−b
.
Therefore, the Distance between any two points is unique;
.
In the Rooted-Tree, there must exist one point
c
c
c whose Depth is Least amongst all other points in the path, moreover,
(
a
−
.
.
.
−
a
2
−
a
1
−
c
−
b
1
−
b
2
.
.
.
−
b
)
(a-...-a_2 - a_1 -c- b_1 -b_2...-b)
(a−...−a2−a1−c−b1−b2...−b) such that
D
[
a
i
]
=
D
[
b
i
]
,
D
[
a
i
]
=
D
[
a
i
+
1
]
−
1
,
D
[
c
]
=
D
[
a
1
]
−
1
D[a_i] = D[b_i], D[a_i] = D[a_{i+1}] -1, D[c] = D[a_1] - 1
D[ai]=D[bi],D[ai]=D[ai+1]−1,D[c]=D[a1]−1;
.
.
We called such point
c
c
c be the Peak (最高点) of the path;
The Distance (距离) between two points is the Value-Sum in the unique Simple-Path between them, it has two types:
.
1
Distance on Point-Value (基于点权的距离), that is all Values are specified by point; (e.g., the Distance of the Simple-Path
a
−
b
−
c
−
d
a-b-c-d
a−b−c−d equals
V
[
a
]
+
V
[
b
]
+
V
[
c
]
+
V
[
d
]
V[a] + V[b] + V[c] + V[d]
V[a]+V[b]+V[c]+V[d])
.
2
Distance on Edge-Value (基于边权的距离), that is all Values are specified by edge; (e.g., the Distance of the Simple-Path
a
−
b
−
c
−
d
a-b-c-d
a−b−c−d equals
V
[
a
−
b
]
+
V
[
b
−
c
]
+
V
[
c
−
d
]
V[a-b] + V[b-c] + V[c-d]
V[a−b]+V[b−c]+V[c−d])
.
Whichever the case of type, the Distance between any two points is always unique (there is no distinguishing for Maximum or Minimum);
--
The Maximal Simple-Path (Maximal-Path) (a点的最长路径) of a point
a
a
a is the Maximum of
D
i
s
t
[
a
]
[
x
]
∀
x
Dist[a][x] \ \ \forall x
Dist[a][x] ∀x;
.
Suppose the Maximal-Path of
a
a
a is
[
a
,
.
.
.
,
b
]
[a, ..., b]
[a,...,b], then
b
b
b maybe not a Leaf-Node (cuz there might exist Negative-Value);
.
The similar definition for the Minimal Simple-Path;
--
The Diameter (直径) of a Tree is the Simple-Path whose value equals to the Maximum of the Maximal-Simple-Path for all points (i.e.,
m
a
x
(
M
a
x
D
i
s
t
[
x
]
)
∀
x
max( MaxDist[x]) \ \ \forall x
max(MaxDist[x]) ∀x);
.
The end-points of a Diameter maybe not Leaf-Node (e.g., there exist Negative-Value)
#求所有点的最长路径#
Let
0
0
0 be the root;
.
Let
D
o
w
n
[
x
]
Down[x]
Down[x] be the Maximal-Distance of
x
x
x that not passing through its Parent-Node (i.e., the Maximal-Distance of
x
x
x in the Sub-Tree rooted by
x
x
x);
.
U
p
[
x
]
Up[x]
Up[x] be the Maximal-Distance of
x
x
x that must passing through its Parent-Node;
.
.
The path of
U
p
[
x
]
Up[x]
Up[x] must be the form
[
x
,
p
p
,
p
,
s
s
]
[x, pp, p, ss]
[x,pp,p,ss] where
p
p
,
s
s
pp,ss
pp,ss are a collection of points,
p
p
,
p
pp, p
pp,p are Ancestor-Node of
x
x
x and
p
p
p is the only points with the Lowest-Depth among all other points,
[
p
,
s
s
]
[p,ss]
[p,ss] is a Path in the Sub-Tree rooted by
p
p
p;
.
.
That is, start at
x
x
x, then moving up to
p
p
pp
pp, and then turn to another child-node of pp
;
.
Obviously, the Maximal-Distance of
x
x
x equals
m
a
x
(
D
o
w
n
[
x
]
,
U
p
[
x
]
)
max( Down[x], Up[x])
max(Down[x],Up[x]);
The brute-method would cost O ( N 2 ) O(N^2) O(N2), here is a O ( N ) O(N) O(N) algorithm;
@Delimiter
Let’s take the example of Distance on Point-Value,
A
[
]
A[]
A[] denotes the Value of points; (it is similar for the case that based on Edge-Value);
.
And suppose that a path must contains at least one-point (e.g., all point-values are Negative, then the Maximal-Distance of any point
x
x
x must be
<
0
< 0
<0 not
=
0
= 0
=0 cuz
x
x
x must contained in the path);
D
o
w
n
[
x
]
Down[x]
Down[x] can be calculated by a DFS dfs_down
;
void dfs_down( int cur, int fa){
Down[ cur] = 0;
for( nex : $(the adjacent-points of `cur`)){
if( nex == fa){ continue;}
dfs_down( nex, cur);
Down[ _cur] = max( Down[ nex], Down[ _cur]);
}
Down[ _cur] += A[ _cur];
}
U
p
[
x
]
Up[x]
Up[x] will use another DFS dfs_up( cur, fa, up)
where up
represents The Maximal-Distance of
f
a
fa
fa without passing through
c
u
r
cur
cur (i.e., the Maximal-Distance of
f
a
fa
fa in the Tree with removing the Sub-Tree rooted by
c
u
r
cur
cur);
r
(a1,a2) (a3)
(b1,b2) (b3)
(c1,c2,c3)
.
When we at dfs_up( b3, a3, up)
, now up
would denote the Maximal-Distance of
a
3
a3
a3 in the Tree
(
r
,
a
1
,
a
2
,
a
3
,
b
1
,
b
2
,
b
3
)
(r,a1,a2,a3,b1,b2,b3)
(r,a1,a2,a3,b1,b2,b3); so, the Answer of
b
3
b3
b3 would be
m
a
x
(
U
p
[
a
3
]
+
A
[
b
3
]
,
D
o
w
n
[
b
3
]
)
max( Up[a3] + A[b3], Down[b3])
max(Up[a3]+A[b3],Down[b3]);
.
Then, we need do some work for the next dfs_up( ci, b3, up?)
; let
d
1
d1
d1 be the Maximum of
D
o
w
n
[
c
1
,
c
2
,
.
.
.
]
Down[c1,c2,...]
Down[c1,c2,...] and
s
o
n
1
son1
son1 corresponds to any
c
i
ci
ci whose
D
o
w
n
[
s
o
n
1
]
=
d
1
Down[son1] = d1
Down[son1]=d1, and
d
2
d2
d2 be the Maximum of
D
o
w
n
[
x
]
∀
x
≠
s
o
n
1
Down[x] \ \ \forall x \neq son1
Down[x] ∀x=son1;
.
.
For the dfs_up( son1, b3, up?)
where up?
should equal to
A
[
b
3
]
+
m
a
x
(
u
p
,
d
2
)
A[b3] + max( up, d2)
A[b3]+max(up,d2);
.
.
For the dfs_up( $(not son1), b3, up?)
where up?
should equal to
A
[
b
3
]
+
m
a
x
(
u
p
,
d
1
)
A[b3] + max( up, d1)
A[b3]+max(up,d1);
void dfs_up( cur, fa, up){
Answer[ _cur] = max( up + A[ cur], Down[ _cur]);
//--
max_down_1 = 0, max_down_2 = 0;
son_1;
for( nex : $(adjacency of `cur`)){
if( nex == fa){ continue;}
if( Down[ nex] > max_down_1){
max_down_2 = max_down_1;
max_down_1 = Down[ nex]; son_1 = nex;
}
else if( Down[ nex] > max_down_2){
max_down_2 = Down[ nex];
}
}
//--
for( nex : $(adjacency of `cur`)){
if( nex == fa){ continue;}
if( nex != son_1){
dfs_up( nex, cur, max( up, max_down_1) + A[cur]);
}
else{
dfs_up( nex, cur, max( up, max_down_2) + A[cur]);
}
}
}
#至少新添加几条边,使得Tree变成EBCC#
problem-link
Let the number of leaves of the Tree (note not Forest) is m m m; (note that m m m is independent from the root of a tree, here the tree has no root; or in other words, whether it has root or whatever the root is, would not affect m m m)
Let all leaves be
a
1
,
.
.
.
,
a
m
a1, ..., am
a1,...,am (suppose that
m
>
1
m > 1
m>1)
There must exist a certain permutation of
a
1
,
.
.
.
,
a
m
a1, ..., am
a1,...,am such that after the below process, a tree becomes a
E
B
C
C
EBCC
EBCC.
for( int i = 1; i + 1 <= m; i += 2){
connect( ai, a_{i+1});
}
if( m & 1){
connect( am, ax); //< ax is any point [a1,...,a_{m-1}]
}
Cuz all edges on a tree are Cut-Edge, those edges must no longer be Cut-Edge in the new graph;
__.
When we connect two leaves
a
i
,
a
j
ai, aj
ai,aj, let the edges on the simple-path
a
i
,
a
j
ai, aj
ai,aj in the Tree are
S
S
S, then after added the new edge, all edges on
S
S
S are no longer Cut-Edge; therefore, our goal is making all simple-path covers all edges on the tree.
__.
T
=
(
a
−
b
)
(
a
−
c
)
(
b
−
d
)
(
b
−
e
)
(
c
−
f
)
(
c
−
g
)
T= (a-b) (a-c) (b-d) (b-e) (c-f) (c-g)
T=(a−b)(a−c)(b−d)(b−e)(c−f)(c−g), all leaves are
d
,
e
,
f
,
g
d,e,f,g
d,e,f,g; if we connect
(
d
−
e
)
(
f
−
g
)
(d-e) (f-g)
(d−e)(f−g) then the edge
(
a
−
b
)
(a-b)
(a−b) is still Cut-Edge; while we connect
(
d
−
g
)
(
e
−
f
)
(d-g) (e-f)
(d−g)(e−f) then there would be no Cut-Edge;
Detail Process:
vector<int> Leaf_order;
dfs( int cur, int fa){
if( `cur` is a leaf){ Leaf_order.push_back( cur);}
for( `nex` : all adjacent-points of `cur){
if( nex == fa){ continue;}
dfs( nex, cur);
}
}
r = any Non-Leaf point;
dfs( r);
Then we connect head = 0, tail = m-1; connect( Leaf_order[head ++], Leaf_order[tail --])
;