对于一棵树 (可以是多叉树), 根为R
祖先
对于任意一个点x
, 该点到R
的 简单路径 (也就是从x
一直往上走到R
的路径)上的所有点
均是x
的 祖先. (x
也是自己的祖先!)
在x
上面的 (即深度小于x
的), 不一定就是x
的祖先. 还必须在x
与R
的 简单路径上
令Depth[ R] = 0
, 则x
的祖先个数为: Depth[ x] + 1
个
简单路径 (正式定义是: 路径上没有重复点)
在树上的简单路径, 比如对于ab
两点间的简单路径
ab
简单路径一定是惟一的, 这由于树的特殊结构决定x = depth[ a], y = depth[ b]
, 则简单路径[a, ..., b]
其对应的depth
有3
种可能情况x, x+1, x+2, ..., y-1, y
, 表示, a
点一直往下走到b
, 路径长度为y - x
x, x-1, x-2, ..., y-1, y
, 表示a
点一直往上走到b
, 路径长度为x - y
x, x+1, x+2, ..., h+1, h, h-1, ..., y-1, y
表示, a
先一直往上走到一个点c
, 然后从c
再一直往下走到b
(x-h) + (y-h)
c
点, 就是ab
的 (LCA), 下面会讲到.公共祖先
S
点集为: 既是a
的祖先, 也是b
的祖先
则, S
点集为 ab
的公共祖先.
S
点集中, 所有点的深度均不同. 其实这个点集, 就是 (aR
简单路径) 与 (bR
简单路径) 的 交集.
最近公共祖先 LCA: least common ancestor
ab
的LCA为: S
点集(即公共祖先中), 深度最大的点, 他是唯一的
求LCA( a, b)
, 假设depth[ a] >= depth[ b]
a
往上走, 移动到和b
同一层(同一深度) 点, 记为aa
d = depth[ a] - depth[ b]
, 要走b
步, 根据倍增原理, b = [0, 1, ..., N]
, 平均下来, 最少是O( logN)
次. 即, 二进制拆分.Fa[ x][ k]
数组, 表示: x
点, 往上走
2
k
2^k
2k步后的祖先N * logN
, 即dfs整个树aa 与 b
是在同一层, 让他俩同时往上走, 走到LCA的儿子Fa[ aa][ 0, 1, 2, ...]
为: a0, a1, a2, a3, ...
Fa[ b][ 0, 1, 2, ...]
为: b0, b1, b2, b3, ...
bool( ai == bi)
这个布尔数组, 记作c
: c0, c1, c2, c3, ...
a2
是LCA, 即: a2 == b2 == LCA
, 则布尔数组为: 0, 0, 1, 1, 1, 1, ...
则, LCA为: 布尔数组中 最左侧的10, 0, 1, 1, 1...
1
,即a2 == b2
, 他确实是公共祖先, 但他不是最近公共祖先!!a2
, 还能往回跳 (即往下跳), 但这里是 倍增 , 只能单向往前走, 不能往回走!ci
为真 (aa 与 b
的公共祖先),就跳过去,这是不对的1
的左侧0
位置 , 即: aa 到 a1点, b 到 b1点
(当然a1 != b1
)a1 b1
继续跳, 仍然是跳到 最左侧的1
的左侧0
位置 , 最终, a b
会跳到: LCA的两个儿子上 (LCA有>=2
个儿子, 多叉树, 但a b
在其中的两个上)算法注意点
对于数, 我们知道, 树根Root
是可以任意的! 即选择[1, ..., N]
哪个点当做树根, 其仍然是一个树!
这可以想象成: 只有树根有一个钉子, 定在那. 其他所有点都下垂
但是, 不同的树根, ab
两点的LCA可能是不同的!
比如, 选择a
作为树根, 自然LCA( a, b) = a
; 选择b
作为树根, 自然LCA( a, b) = b
因此, 只有树根确定了, LCA
才能确定.
… 但是, 涉及到LCA的问题, 不一定题目会给出固定的树根! 比如, 树上前缀和/差分, 都会涉及到LCA
… 但是, 树根选择哪个都是正确的, 都可以正确的求出前缀和/差分, 只要找到LCA即可, 不管是在哪个树根下.
让Depth[ Root] = 0
, 这样做的好处是: 对于一点x
, x 与 Root
的简单路径上 正好有 Depth[ x]
条边.
也就是, x
点, 往上跳Depth[ x]
步, 就是Root
设置一个int Log[ N]
数组, Log[ a] = b
满足: 2^b <= a, 且b最大
这样, 每次求lca( a, b)
时, 往上跳时, 不需要从 Log( N) 从而产生一堆非法值
而直接从Log( Depth[ a])
开始.
… 而且, 还有个好处是: 当constexpr int N_ = xx;
点数改变了, 只需修改下: Fa[ N_ ][ 21]
中的21
这个数值
… 其他代码不用改, 因为代码里用的都是Log
, 而Log
会根据N_
的大小来进行循环
… Log
的大小是: int Log[ N_];
, 也不用动.
Log[ 0]
是未定义的, Fa[ Root][ >= 0]
都是未定义的, 也都是非法值.
要避开这些.
题目链接
LITERAL_ int N_ = int( 5e5) + 5;
int N, M, Root; //< (点数) (查询数) (根)
int Head[ N_], Ver[ N_ * 2], Nex[ N_ * 2], Edges;
int Log[ N_]; //< Log[ a] = b; 2^b <= a, 最大的b
int Depth[ N_], Fa[ N_][ 21];
//--
void Build_graph(){
Edges = 0;
memset( Head, -1, sizeof( Head));
}
void Add_edge( PAR_( int, _a), PAR_( int , _b)){
Ver[ Edges] = _b;
Nex[ Edges] = Head[ _a];
Head[ _a] = Edges;
++ Edges;
}
void Dfs_lca( PAR_( int, _cur), PAR_( int, _fa)){
for( int nex, i = Head[ _cur]; ~i; i = Nex[ i]){
nex = Ver[ i];
if( nex != _fa){
Depth[ nex] = Depth[ _cur] + 1;
//--
Fa[ nex][ 0] = _cur;
FOR_( step, 1, Log[ Depth[ nex]], 1){
Fa[ nex][ step] = Fa[ Fa[ nex][ step - 1]][ step - 1];
// DEBUG_( nex, step, Fa[ nex][ step]); //< 在`Build_lca()`完后, 最好调试测试下
}
//--
Dfs_lca( nex, _cur);
}
}
}
void Build_lca(){
{ //* build `Log`
FOR_( i, 1, N, 1){
if( 1 == i){
Log[ 1] = 0;
}
else{
if( ( 1 << ( Log[ i - 1] + 1)) == i){
Log[ i] = Log[ i - 1] + 1;
}
else{
Log[ i] = Log[ i - 1];
}
}
}
}
//--
Depth[ Root] = 0;
Dfs_lca( Root, -1);
}
int Lca( PAR_( int, _a), PAR_( int, _b)){
int low = _a, hig = _b;
if( Depth[ low] < Depth[ hig]){
swap( low, hig);
}
while( Depth[ low] != Depth[ hig]){
low = Fa[ low][ Log[ Depth[ low] - Depth[ hig]]];
}
if( low == hig){ //< 如果不判断, 假如low=Root, 则下面代码中的Log[ 0]是个未定义行为
return low;
}
FOR_RE_( i, Log[ Depth[ low]], 0, 1){ //< 不是 `Log[ low]`!
if( Fa[ low][ i] != Fa[ hig][ i]){
low = Fa[ low][ i];
hig = Fa[ hig][ i];
}
}
assert( Fa[ low][ 0] == Fa[ hig][ 0]);
return Fa[ low][ 0];
}
void __Solve(){
Build_graph();
scanf("%d%d%d", &N, &M, &Root);
FOR_( i, 1, N-1, 1){
int a, b;
scanf("%d%d", &a, &b);
Add_edge( a, b);
Add_edge( b, a);
}
Build_lca();
FOR_( i, 1, M, 1){
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", Lca( a, b));
}
}