最近共同祖先 Farach-Colton & Bender 算法(FC&B)

最近共同祖先 Farach-Colton & Bender 算法(FC&B)最近共同祖先 FC amp B 算法 Lowest Common Ancestor Farach Colton amp Bender Algorithm 假设 G G G 是一棵树 对于每一个形式为 u

大家好,我是讯享网,很高兴认识大家。

最近共同祖先:FC&B算法(Lowest Common Ancestor : Farach-Colton & Bender Algorithm)

假设 G G G 是一棵树。对于每一个形式为 ( u , v ) (u,v) (u,v) 的查询,我们想找到节点 u u u v v v 的最近共同祖先,也就是说,我们想找到一个节点 w w w ,它位于从 u u u v v v 到根节点的路径上,如果有多个节点,我们选择离根节点最远的那个。换句话说,所需的节点 w w w u u u v v v 的最近祖先。特别情况下,如果 u u u v v v 的祖先,那么 u u u 就是它们的最近共同祖先。

本文将描述的算法是由 Farach-Colton 和 Bender 共同开发的,是一种渐进式的最优算法。


算法

我们使用经典的 LCA 问题归约为 RMQ 问题。用 DFS 遍历树的所有节点,并保留一个包含所有访问过的节点和这些节点的高度的数组。两个节点 u u u v v v 的 LCA 是在遍历中出现的 u u u v v v 之间的节点,它的高度最小。

在下图(表)中,你可以看到一种情况下的欧拉遍历和访问过的节点以及它们的高度。
在这里插入图片描述
讯享网
在 Lowest Common Ancestor (可能需要科学上网)一文中读到更多关于这种归约的信息。在上述这篇文章中,一个范围的最小值是通过平方分解求得的 O ( N ) O\left( \sqrt{N}\right) O(N ) 或者用线段树求得的 O ( log ⁡ N ) O\left( \log N\right) O(logN)

而在本文中,我们将探讨如何能在 O ( 1 ) O\left( 1\right) O(1) 的时间内解决给定的范围最小值查询,同时仍然只用 O ( N ) O\left( N\right) O(N) 的时间复杂度进行预处理。

需要注意的是,归约的 RMQ 问题是非常具体的:数组中任何两个相邻的元素正好相差 1 1 1(因为数组的元素只不过是按遍历顺序访问的节点的高度,我们要么去找一个后代,在这种情况下,下一个元素大 1 1 1 ;要么回到祖代,在这种情况下,下一个元素小 1 1 1 )。Farach-Colton 和 Bender 算法描述的正是这种专门的 RMQ 问题的解决方案。

让我们用 A A A 来表示我们要对其进行范围最小查询的数组。而 N N N 将是 A A A 的大小。

有一个简单的数据结构,需要进行 O ( N log ⁡ N ) O\left( N \log N\right) O(NlogN) 的预处理,我们可以用它来解决 RMQ 问题,其中每个查询为 O ( 1 ) O(1) O(1)稀疏表 。我们创建一个表 T T T ,其中每个元素 T [ i ] [ j ] T[i][j] T[i][j] 等于 A A A 在区间 [ i , i + 2 j − 1 ] [i,i+2^{j}-1] [i,i+2j1] 中的最小值。显然 0 ≤ j ≤ ⌈ log ⁡ N ⌉ 0\leq j\leq\lceil \log N\rceil 0jlogN ,因此稀疏表的大小将是 O ( N log ⁡ N ) O\left( N \log N\right) O(NlogN) ,你可以在 O ( N log ⁡ N ) O(N \log N) O(NlogN) 内轻松建立表,其中 T [ i ] [ j ] = m i n ( T [ i ] [ j − 1 ] , T [ i + 2 j − 1 ] [ j − 1 ] ) T[i][j]=min(T[i][j-1],T[i+2^{j-1}][j-1]) T[i][j]=min(T[i][j1],T[i+2j1][j1])

我们如何用这种数据结构在 O ( 1 ) O(1) O(1) 内回答一条查询 RMQ ?假设收到的查询是 [ l , r ] [l,r] [l,r] ,那么答案是 m i n ( T [ l ] [ s z ] , T [ r − 2 s z + 1 ] [ s z ] ) min(T[l][sz],T[r-2^{sz}+1][sz]) min(T[l][sz],T[r2sz+1][sz]) ,其中 s z sz sz 是最大的指数,使 2 s z 2^{sz} 2sz 不大于范围长度 r − l + 1 r-l+1 rl+1 。事实上,我们可以把范围 [ l , r ] [l,r] [l,r] 覆盖在两个长度为 2 s z 2^{sz} 2sz 的片段上,一端从 l l l 开始,另一端在 r r r 结束。这些片段是重叠的,但这并不影响我们的计算。为了真正实现每个查询的时间复杂度为 O ( 1 ) O(1) O(1) ,我们需要知道从 1 1 1 N N N 的所有可能的长度的 s z sz sz 值,这是很容易预先计算的。

现在我们想将预处理的复杂度降低到 O ( N ) O\left( N\right) O(N)

我们把数组 A A A 分成大小为 K = log ⁡ 2 N 2 K=\dfrac{\log_{2} N}{2} K=2log2N 的块。对于每个块,我们计算出最小的元素,并将其存储在数组 B B B 中, B B B 的大小为 N K \dfrac{N}{K} KN 。我们从数组 B B B 构建一个稀疏表。它的规模和时间的复杂度为: N K log ⁡ ( N K ) = 2 N l o g N log ⁡ ( 2 N log ⁡ N ) = 2 N log ⁡ N ( 1 + log ⁡ ( N log ⁡ N ) ) ≤ 2 N log ⁡ N + 2 N = O ( N ) \dfrac{N}{K} \log \left( \dfrac{N}{K}\right)=\dfrac{2N}{log N} \log \left( \dfrac{2N}{ \log N}\right)=\dfrac{2N}{ \log N}\left( 1+ \log\left( \dfrac{N}{ \log N}\right)\right)\leq \dfrac{2N}{ \log N}+2N=O\left( N\right) KNlog(KN)=logN2Nlog(logN2N)=logN2N(1+log(logNN))logN2N+2N=O(N)

现在我们只需要学习如何快速解决每个区块内的最小范围查询。事实上,如果收到的范围最小查询是 [ l , r ] [l,r] [l,r] ,并且 l l l r r r 在不同的区块中,那么答案就是以下三个值的最小值:从 l l l 开始的 l l l 区块的后缀的最小值,从 r r r 结束的 r r r 区块的前缀的最小值,以及这些区块之间的最小值。这之间的区块的最小值可以用稀疏表在 O ( 1 ) O\left( 1\right) O(1) 内回答。因此,我们只剩下区块内的最小值查询。

这里我们将利用数组的属性。请记住,数组中只是树中的高度值将总是相差 1 1 1 。如果我们去掉一个区块的第一个元素,并将其与区块中的其他每一项相减,那么每个区块都可以由一个长度为 K − 1 K-1 K1 的序列来识别,该序列由数字 1 1 1 0 0 0 组成,由于这些区块非常小,只有几个不同的序列可以出现。可能的序列数量为: 2 K − 1 = 2 log ⁡ N 2 − 1 = 2 log ⁡ N 2 = N 2 2^{K-1}=2^{\dfrac{\log N}{2}-1}=\dfrac{\sqrt{2^{ \log N}}}{2}=\dfrac{\sqrt{N}}{2} 2K1=22logN1=22logN =2N

因此,不同区块的数量是 O ( N ) O\left( \sqrt{N}\right) O(N ) ,因此我们可以在 O ( N K 2 ) = O ( N log ⁡ 2 N ) = O ( N ) O\left( \sqrt{N}K^{2}\right)= O\left( \sqrt{N}\log ^{2}N\right)=O\left( N\right) O(N K2)=O(N log2N)=O(N) 时间内预先计算出所有不同区块内的范围最小值查询结果。为了实现这一点,我们可以用一个长度为 K − 1 K-1 K1 的比特掩码来描述一个区块的特征(适合于标准int),并将最小值的索引存储在一个大小为 O ( N log ⁡ 2 N ) O\left( \sqrt{N}\log ^{2}N\right) O(N log2N) 的数组 b l o c k [ m a s k ] [ l ] [ r ] block[mask][l][r] block[mask][l][r] 中。

因此,我们学会了如何在每个区块内预先计算范围内的最小查询,以及在一个区块范围内的范围内的最小查询,所有这些都在 O ( N ) O\left( N\right) O(N) 的时间中。通过这些预计算,我们最多使用四个预先计算的值:包含 l l l 的区块的最小值,包含 r r r 的区块的最小值,以及它们之间区块重叠段的两个最小值,就可以在 O ( 1 ) O\left( 1\right) O(1) 内解决每个查询。


实现

int n; vector<vector<int>> adj; int block_size, block_cnt; vector<int> first_visit; vector<int> euler_tour; vector<int> height; vector<int> log_2; vector<vector<int>> st; vector<vector<vector<int>>> blocks; vector<int> block_mask; void dfs(int v, int p, int h) { 
    first_visit[v] = euler_tour.size(); euler_tour.push_back(v); height[v] = h; for (int u : adj[v]) { 
    if (u == p) continue; dfs(u, v, h + 1); euler_tour.push_back(v); } } int min_by_h(int i, int j) { 
    return height[euler_tour[i]] < height[euler_tour[j]] ? i : j; } void precompute_lca(int root) { 
    // get euler tour & indices of first occurences first_visit.assign(n, -1); height.assign(n, 0); euler_tour.reserve(2 * n); dfs(root, -1, 0); // precompute all log values int m = euler_tour.size(); log_2.reserve(m + 1); log_2.push_back(-1); for (int i = 1; i <= m; i++) log_2.push_back(log_2[i / 2] + 1); block_size = max(1, log_2[m] / 2); block_cnt = (m + block_size - 1) / block_size; // precompute minimum of each block and build sparse table st.assign(block_cnt, vector<int>(log_2[block_cnt] + 1)); for (int i = 0, j = 0, b = 0; i < m; i++, j++) { 
    if (j == block_size) j = 0, b++; if (j == 0 || min_by_h(i, st[b][0]) == i) st[b][0] = i; } for (int l = 1; l <= log_2[block_cnt]; l++) { 
    for (int i = 0; i < block_cnt; i++) { 
    int ni = i + (1 << (l - 1)); if (ni >= block_cnt) st[i][l] = st[i][l-1]; else st[i][l] = min_by_h(st[i][l-1], st[ni][l-1]); } } // precompute mask for each block block_mask.assign(block_cnt, 0); for (int i = 0, j = 0, b = 0; i < m; i++, j++) { 
    if (j == block_size) j = 0, b++; if (j > 0 && (i >= m || min_by_h(i - 1, i) == i - 1)) block_mask[b] += 1 << (j - 1); } // precompute RMQ for each unique block int possibilities = 1 << (block_size - 1); blocks.resize(possibilities); for (int b = 0; b < block_cnt; b++) { 
    int mask = block_mask[b]; if (!blocks[mask].empty()) continue; blocks[mask].assign(block_size, vector<int>(block_size)); for (int l = 0; l < block_size; l++) { 
    blocks[mask][l][l] = l; for (int r = l + 1; r < block_size; r++) { 
    blocks[mask][l][r] = blocks[mask][l][r - 1]; if (b * block_size + r < m) blocks[mask][l][r] = min_by_h(b * block_size + blocks[mask][l][r], b * block_size + r) - b * block_size; } } } } int lca_in_block(int b, int l, int r) { 
    return blocks[block_mask[b]][l][r] + b * block_size; } int lca(int v, int u) { 
    int l = first_visit[v]; int r = first_visit[u]; if (l > r) swap(l, r); int bl = l / block_size; int br = r / block_size; if (bl == br) return euler_tour[lca_in_block(bl, l % block_size, r % block_size)]; int ans1 = lca_in_block(bl, l % block_size, block_size - 1); int ans2 = lca_in_block(br, 0, r % block_size); int ans = min_by_h(ans1, ans2); if (bl + 1 < br) { 
    int l = log_2[br - bl - 1]; int ans3 = st[bl+1][l]; int ans4 = st[br - (1 << l)][l]; ans = min_by_h(ans, min_by_h(ans3, ans4)); } return euler_tour[ans]; } 

讯享网
小讯
上一篇 2025-03-05 09:59
下一篇 2025-03-05 22:01

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/35931.html