update
This commit is contained in:
parent
c6b7d10884
commit
bdb8bea73f
@ -1,9 +1,7 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <queue>
|
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <exception>
|
|
||||||
|
|
||||||
using ll = int64_t;
|
using ll = int64_t;
|
||||||
using std::cin, std::cout;
|
using std::cin, std::cout;
|
||||||
|
@ -226,3 +226,446 @@ int main(){
|
|||||||
- 对于更大规模的数据,需要优化数据结构和算法,例如使用更高效的方式存储DP信息(如并查集、最低公共祖先LCA等技术),以降低时间和空间复杂度。
|
- 对于更大规模的数据,需要优化数据结构和算法,例如使用更高效的方式存储DP信息(如并查集、最低公共祖先LCA等技术),以降低时间和空间复杂度。
|
||||||
|
|
||||||
希望这些解释和注释对您理解和优化代码有所帮助!如果有进一步的问题,欢迎继续交流。
|
希望这些解释和注释对您理解和优化代码有所帮助!如果有进一步的问题,欢迎继续交流。
|
||||||
|
|
||||||
|
# 100pts解
|
||||||
|
### 题目概述
|
||||||
|
|
||||||
|
本题涉及在一棵带权树上计算不同类型的暴龙(类似于节点类型)的红温值(即同类型暴龙任意两点之间的最大距离)。具体来说,给定一棵有 $n$ 个节点和边权的树,树上的每个节点都有 $k$ 种暴龙中的一种。对于每一种暴龙类型,定义其红温值为同类型暴龙之间的最大距离;如果该类型的暴龙数量小于2,则红温值为0。
|
||||||
|
|
||||||
|
### 解题思路
|
||||||
|
|
||||||
|
1. **树的预处理**:
|
||||||
|
- **深度优先搜索 (DFS)**:计算每个节点的深度 (`dpt`),以及从根节点到该节点的距离 (`w`)。
|
||||||
|
- **二进制跳表 (Binary Lifting)**:预处理每个节点的祖先节点,以便快速计算任意两个节点的最近公共祖先 (LCA)。
|
||||||
|
- **距离计算**:利用 LCA,计算任意两个节点之间的距离。
|
||||||
|
|
||||||
|
2. **计算每种暴龙的红温值**:
|
||||||
|
- 对于每种暴龙类型,首先选取其中任意两个节点,计算它们之间的距离作为初始的最大距离 (`ans`)。
|
||||||
|
- 逐一遍历该类型的所有节点,对于每个新的节点,计算它与当前两个端点的距离,更新端点和 `ans` 的值以确保 `ans` 始终为当前类型的最大距离。
|
||||||
|
|
||||||
|
### 代码详解与注释
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <cstdint>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <bitset>
|
||||||
|
|
||||||
|
// 使用类型别名提高代码可读性
|
||||||
|
using ll = int64_t;
|
||||||
|
using std::cin, std::cout;
|
||||||
|
|
||||||
|
// 常量定义
|
||||||
|
const ll maxn = 5e5 + 5; // 最大节点数
|
||||||
|
const ll L = 20; // 二进制跳表的层数,满足 n <= 5e5 时 2^20 > 5e5
|
||||||
|
|
||||||
|
// 全局变量
|
||||||
|
ll n, k; // 节点数和暴龙种类数
|
||||||
|
ll a[maxn]; // a[i] 表示节点 i 的暴龙类型
|
||||||
|
ll fa[maxn][L]; // fa[u][j] 表示节点 u 的 2^j 祖先节点
|
||||||
|
ll dpt[maxn]; // dpt[u] 表示节点 u 的深度
|
||||||
|
ll w[maxn]; // w[u] 表示节点 u 与根节点的距离
|
||||||
|
std::vector<ll> g[maxn]; // g[i] 存储所有类型为 i 的节点
|
||||||
|
std::vector<std::pair<ll, ll>> adj[maxn]; // adj[u] 存储节点 u 的所有邻接点及对应边权
|
||||||
|
|
||||||
|
// DFS 预处理,计算深度和从根到每个节点的距离
|
||||||
|
void dfs(const ll &fth, const ll &u){
|
||||||
|
fa[u][0] = fth; // 记录节点 u 的直接父节点
|
||||||
|
for(auto& [v, ww] : adj[u]){
|
||||||
|
if(v == fth) continue; // 避免回到父节点
|
||||||
|
dpt[v] = dpt[u] + 1; // 更新子节点的深度
|
||||||
|
w[v] = w[u] + ww; // 更新子节点到根的距离
|
||||||
|
dfs(u, v); // 递归遍历子节点
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化二进制跳表,用于快速查询 LCA
|
||||||
|
void initfa(){
|
||||||
|
for(ll j = 1; j < L; j++){ // 对于每一层
|
||||||
|
for(ll u = 1; u <= n; u++){ // 对于每个节点
|
||||||
|
fa[u][j] = fa[fa[u][j-1]][j-1]; // 2^j 祖先等于 2^(j-1) 的祖先的 2^(j-1) 祖先
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算两个节点的最近公共祖先 (LCA)
|
||||||
|
ll lca(ll u, ll v){
|
||||||
|
if(dpt[u] < dpt[v]) std::swap(u, v); // 确保 u 的深度不小于 v
|
||||||
|
ll dif = dpt[u] - dpt[v];
|
||||||
|
// 将 u 提升到与 v 在同一深度
|
||||||
|
for(ll j = 0; dif > 0; j++, dif >>= 1){
|
||||||
|
if(dif & 1){
|
||||||
|
u = fa[u][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(u == v) return u; // 如果 v 是 u 的祖先,直接返回
|
||||||
|
// 从高位到低位同时提升 u 和 v,找到 LCA
|
||||||
|
for(ll j = L-1; j >= 0; j--){
|
||||||
|
if(fa[u][j] != fa[v][j]){
|
||||||
|
u = fa[u][j];
|
||||||
|
v = fa[v][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fa[u][0]; // 返回 LCA
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算两个节点之间的距离
|
||||||
|
ll dis(const ll &u, const ll &v){
|
||||||
|
return w[u] + w[v] - 2 * w[lca(u, v)];
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(){
|
||||||
|
// 关闭同步,提高输入输出速度
|
||||||
|
std::ios::sync_with_stdio(false);
|
||||||
|
std::cin.tie(nullptr);
|
||||||
|
std::cout.tie(nullptr);
|
||||||
|
|
||||||
|
// 输入节点数和暴龙种类数
|
||||||
|
cin >> n >> k;
|
||||||
|
// 输入每个节点的暴龙类型,并将节点编号加入对应类型的列表
|
||||||
|
for(ll i = 1; i <= n; i++){
|
||||||
|
cin >> a[i];
|
||||||
|
g[a[i]].push_back(i);
|
||||||
|
}
|
||||||
|
// 输入树的边
|
||||||
|
for(ll i = 1; i < n; i++){
|
||||||
|
ll u, v, c;
|
||||||
|
cin >> u >> v >> c;
|
||||||
|
adj[u].emplace_back(v, c);
|
||||||
|
adj[v].emplace_back(u, c);
|
||||||
|
}
|
||||||
|
// 从根节点 1 开始进行 DFS 预处理
|
||||||
|
dfs(0, 1);
|
||||||
|
// 初始化二进制跳表
|
||||||
|
initfa();
|
||||||
|
|
||||||
|
// 对于每一种暴龙类型,计算其红温值
|
||||||
|
for(ll i = 1; i <= k; i++){
|
||||||
|
// 如果该类型的暴龙数量小于2,红温值为0
|
||||||
|
if(g[i].size() < 2){
|
||||||
|
cout << "0\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 初始化第一个端点 u 和第二个端点 v
|
||||||
|
ll u = g[i][0];
|
||||||
|
ll v = g[i][1];
|
||||||
|
// 计算初始的最大距离
|
||||||
|
ll ans = dis(u, v);
|
||||||
|
// 遍历该类型的剩余节点,更新端点和最大距离
|
||||||
|
for(ll j = 2; j < (ll)g[i].size(); j++){
|
||||||
|
ll du = dis(u, g[i][j]); // 当前节点与端点 u 的距离
|
||||||
|
ll dv = dis(v, g[i][j]); // 当前节点与端点 v 的距离
|
||||||
|
if(du > dv){
|
||||||
|
if(du > ans){
|
||||||
|
v = g[i][j]; // 更新 v 为当前节点
|
||||||
|
ans = du; // 更新最大距离
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
if(dv > ans){
|
||||||
|
u = g[i][j]; // 更新 u 为当前节点
|
||||||
|
ans = dv; // 更新最大距离
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 输出该类型的红温值
|
||||||
|
cout << ans << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 详细解释
|
||||||
|
|
||||||
|
1. **输入与预处理**:
|
||||||
|
- **节点信息**:首先读取节点数 `n` 和暴龙种类数 `k`。然后读取每个节点的暴龙类型,存储在数组 `a` 中,并将每个节点编号加入对应类型的列表 `g[i]`。
|
||||||
|
- **树的构建**:接下来读取 `n-1` 条边,每条边连接两个节点并有一个边权,将这些信息存储在邻接表 `adj` 中。
|
||||||
|
|
||||||
|
2. **深度优先搜索 (DFS) 和二进制跳表初始化**:
|
||||||
|
- **DFS**:从根节点(这里假设为节点1)开始遍历整棵树,计算每个节点的深度 `dpt`,以及从根节点到该节点的距离 `w`。同时,记录每个节点的直接父节点 `fa[u][0]`。
|
||||||
|
- **二进制跳表 (`initfa` 函数)**:构建一个 `fa` 数组,其中 `fa[u][j]` 表示节点 `u` 的 `2^j` 祖先节点。通过动态规划的方式,`fa[u][j] = fa[fa[u][j-1]][j-1]`,从而可以在对数时间内找到任意节点的祖先,用于高效计算 LCA。
|
||||||
|
|
||||||
|
3. **最近公共祖先 (LCA) 和距离计算**:
|
||||||
|
- **LCA (`lca` 函数)**:利用二进制跳表,首先将深度较深的节点提升到与另一个节点同一深度,然后同时提升两者直到找到 LCA。
|
||||||
|
- **距离 (`dis` 函数)**:利用 LCA 计算两个节点之间的距离,即 `w[u] + w[v] - 2*w[lca(u, v)]`。
|
||||||
|
|
||||||
|
4. **计算每种暴龙的红温值**:
|
||||||
|
- 对于每种暴龙类型 `i`:
|
||||||
|
- 如果该类型的节点数少于2,输出0。
|
||||||
|
- 否则,选择该类型中的任意两个节点作为初始的两个端点 `u` 和 `v`,计算它们之间的距离作为初始的红温值 `ans`。
|
||||||
|
- 遍历该类型的剩余节点,每遇到一个新节点,计算它与当前两个端点的距离 `du` 和 `dv`。
|
||||||
|
- 如果 `du > dv`:
|
||||||
|
- 如果 `du > ans`,则将端点 `v` 更新为当前节点,`ans` 更新为 `du`。
|
||||||
|
- 否则:
|
||||||
|
- 如果 `dv > ans`,则将端点 `u` 更新为当前节点,`ans` 更新为 `dv`。
|
||||||
|
- 这种方法类似于在树上寻找直径的双 BFS 方法,通过两次遍历找到最大距离。
|
||||||
|
|
||||||
|
5. **输出结果**:
|
||||||
|
- 对每一种暴龙类型,输出其对应的红温值。
|
||||||
|
|
||||||
|
### 算法复杂度分析
|
||||||
|
|
||||||
|
- **预处理阶段**:
|
||||||
|
- **DFS**:$O(n)$。
|
||||||
|
- **二进制跳表初始化**:$O(n \log n)$。
|
||||||
|
|
||||||
|
- **每种类型的红温值计算**:
|
||||||
|
- 对于每种类型,遍历其所有节点,计算距离。
|
||||||
|
- 总体复杂度为 $O(k \cdot m \cdot \log n)$,其中 $m$ 是每种类型的节点数。
|
||||||
|
|
||||||
|
- **整体复杂度**:由于 $k \leq n$ 且每种类型的节点总和为 $n$,整体复杂度为 $O(n \log n + n \log n) = O(n \log n)$,满足题目约束。
|
||||||
|
|
||||||
|
### 代码优化技巧
|
||||||
|
|
||||||
|
1. **高效输入输出**:使用 `std::ios::sync_with_stdio(false)` 和 `std::cin.tie(nullptr)` 关闭同步和绑定,提高输入输出速度,避免因大量数据导致超时。
|
||||||
|
2. **使用静态数组**:利用预定义大小的静态数组(如 `a[maxn]`, `fa[maxn][L]` 等)提高访问速度和内存利用效率。
|
||||||
|
3. **按类型存储节点**:将相同类型的节点集中存储在 `g[i]` 中,便于后续遍历和计算。
|
||||||
|
|
||||||
|
### 总结
|
||||||
|
|
||||||
|
本题通过有效地预处理树的深度、距离和 LCA,结合类似于双 BFS 的方法,能够在较低的时间复杂度内求解每一种暴龙类型的红温值。关键在于利用二进制跳表实现高效的 LCA 查询,以及聪明地遍历节点以找到最大距离。这种方法在处理大规模树型数据时尤为有效,适用于类似的树上最远点对问题。
|
||||||
|
### 详细解释 LCA(最近公共祖先)与二进制位的关系
|
||||||
|
|
||||||
|
在树结构中,**最近公共祖先(Lowest Common Ancestor, LCA)** 是指两个节点在树中共同的最深的祖先节点。高效地计算 LCA 对于许多树相关的问题至关重要,例如本题中用于计算节点间距离。
|
||||||
|
|
||||||
|
本文将详细解释如何利用 **二进制提升(Binary Lifting)** 技术来高效计算 LCA,特别是如何通过二进制位的概念实现这一过程。
|
||||||
|
|
||||||
|
#### 二进制提升概述
|
||||||
|
|
||||||
|
**二进制提升** 是一种预处理技术,用于在树上快速查询任意两个节点的 LCA。其核心思想是为每个节点预计算并存储其 2^j 祖先节点,其中 j 从 0 到 log₂n。这允许我们以对数时间复杂度(O(log n))跳跃树的高度,从而快速找到 LCA。
|
||||||
|
|
||||||
|
#### 关键概念
|
||||||
|
|
||||||
|
1. **2^j 祖先节点(fa[u][j])**:
|
||||||
|
- 对于树中的每个节点 u 和每个 j(0 ≤ j ≤ log₂n),fa[u][j] 表示节点 u 的第 2^j 级祖先节点。
|
||||||
|
- 例如,fa[u][0] 是 u 的直接父节点,fa[u][1] 是 u 的祖父节点(2^1 = 2 跳),fa[u][2] 是 u 的曾祖父节点(2^2 = 4 跳),依此类推。
|
||||||
|
|
||||||
|
2. **节点深度(dpt)**:
|
||||||
|
- dpt[u] 表示节点 u 相对于根节点的深度(根节点深度为 0)。
|
||||||
|
|
||||||
|
3. **距离表示与二进制位**:
|
||||||
|
- 任意整数可以用二进制表示为若干个 2^j 的和。例如,13 = 8 + 4 + 1 = 2^3 + 2^2 + 2^0,对应二进制 1101。
|
||||||
|
- 这种表示方式允许我们通过二进制位选择性地跳跃若干步,从而高效地移动节点。
|
||||||
|
|
||||||
|
#### 计算 LCA 的步骤与二进制位的应用
|
||||||
|
|
||||||
|
以下为计算两个节点 u 和 v 的 LCA 的主要步骤,详细解释了二进制位的应用:
|
||||||
|
|
||||||
|
1. **确保 u 在较深的位置**:
|
||||||
|
```cpp
|
||||||
|
if(dpt[u] < dpt[v]) std::swap(u, v);
|
||||||
|
```
|
||||||
|
确保节点 u 比节点 v 深,这样我们只需处理 u 的提升。
|
||||||
|
|
||||||
|
2. **计算深度差并用二进制表示**:
|
||||||
|
```cpp
|
||||||
|
ll dif = dpt[u] - dpt[v];
|
||||||
|
```
|
||||||
|
计算节点 u 和节点 v 之间的深度差 dif。
|
||||||
|
|
||||||
|
3. **将 u 提升到与 v 同一深度**:
|
||||||
|
```cpp
|
||||||
|
for(ll j = 0; dif > 0; j++, dif >>= 1){
|
||||||
|
if(dif & 1){
|
||||||
|
u = fa[u][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **二进制位检查**:通过检查 dif 的每一位(从低到高),确定需要提升的步数。
|
||||||
|
- **位的意义**:每一位 j(从 0 开始)对应着 2^j 步的提升。
|
||||||
|
- **示例**:
|
||||||
|
- 若 dif = 13(即二进制 1101),则表示需要提升 2^0 + 2^2 + 2^3 = 1 + 4 + 8 = 13 步。
|
||||||
|
- 循环过程中:
|
||||||
|
- j=0: dif & 1 = 1,提升 2^0 = 1 步,u = fa[u][0]
|
||||||
|
- j=1: dif >> 1 = 6,dif & 1 = 0,跳过
|
||||||
|
- j=2: dif >> 2 = 3,dif & 1 = 1,提升 2^2 = 4 步,u = fa[u][2]
|
||||||
|
- j=3: dif >> 3 = 1,dif & 1 = 1,提升 2^3 = 8 步,u = fa[u][3]
|
||||||
|
- 最终,u 被提升 13 步,达到与 v 同一深度。
|
||||||
|
|
||||||
|
4. **检查是否已经找到 LCA**:
|
||||||
|
```cpp
|
||||||
|
if(u == v) return u;
|
||||||
|
```
|
||||||
|
如果提升后 u 和 v 重合,则 LCA 为 u(或 v)。
|
||||||
|
|
||||||
|
5. **同步提升 u 和 v 以找到 LCA**:
|
||||||
|
```cpp
|
||||||
|
for(ll j = L - 1; j >= 0; j--){
|
||||||
|
if(fa[u][j] != fa[v][j]){
|
||||||
|
u = fa[u][j];
|
||||||
|
v = fa[v][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fa[u][0];
|
||||||
|
```
|
||||||
|
- **从高位到低位**:从最大的 j 开始,逐步检查并提升 u 和 v。
|
||||||
|
- **条件判断**:若 fa[u][j] != fa[v][j],则同时提升 u 和 v 到它们各自的第 2^j 祖先。
|
||||||
|
- **找到 LCA**:最终,u 和 v 的父节点(fa[u][0])为 LCA。
|
||||||
|
|
||||||
|
**二进制位的应用**:
|
||||||
|
- 通过从高到低的二进制位检查,我们确保每一步尽可能多地提升 u 和 v,快速缩小它们的差距。
|
||||||
|
- 这种方式最大限度地利用了二进制表示中的高位,从而在对数步内完成提升,达到高效计算的目的。
|
||||||
|
|
||||||
|
#### 示例说明
|
||||||
|
|
||||||
|
让我们通过一个具体示例来说明二进制位在 LCA 计算中的应用。
|
||||||
|
|
||||||
|
**示例树结构**:
|
||||||
|
|
||||||
|
```
|
||||||
|
1
|
||||||
|
/ \
|
||||||
|
2 3
|
||||||
|
/ / \
|
||||||
|
4 5 6
|
||||||
|
/ \
|
||||||
|
7 8
|
||||||
|
```
|
||||||
|
|
||||||
|
**节点深度(dpt)与 fa 表**:
|
||||||
|
|
||||||
|
| 节点 | dpt | fa[u][0] | fa[u][1] | fa[u][2] | fa[u][3] |
|
||||||
|
|------|-----|----------|----------|----------|----------|
|
||||||
|
| 1 | 0 | 0 | 0 | 0 | 0 |
|
||||||
|
| 2 | 1 | 1 | 0 | 0 | 0 |
|
||||||
|
| 3 | 1 | 1 | 0 | 0 | 0 |
|
||||||
|
| 4 | 2 | 2 | 1 | 0 | 0 |
|
||||||
|
| 5 | 2 | 3 | 1 | 0 | 0 |
|
||||||
|
| 6 | 2 | 3 | 1 | 0 | 0 |
|
||||||
|
| 7 | 3 | 4 | 2 | 1 | 0 |
|
||||||
|
| 8 | 3 | 4 | 2 | 1 | 0 |
|
||||||
|
|
||||||
|
**查询 LCA(7, 5)**:
|
||||||
|
|
||||||
|
1. **节点深度**:
|
||||||
|
- dpt[7] = 3
|
||||||
|
- dpt[5] = 2
|
||||||
|
|
||||||
|
2. **保证 u 深度 >= v 深度**:
|
||||||
|
- u = 7, v = 5
|
||||||
|
|
||||||
|
3. **计算深度差 dif = 3 - 2 = 1(二进制 0001)**:
|
||||||
|
- 0001 表示需要提升 2^0 = 1 步。
|
||||||
|
|
||||||
|
4. **提升 u 7 一步**:
|
||||||
|
- fa[7][0] = 4 → u = 4
|
||||||
|
|
||||||
|
5. **比较 u 和 v**:
|
||||||
|
- u = 4, v = 5
|
||||||
|
- u ≠ v,因此需要继续提升。
|
||||||
|
|
||||||
|
6. **同步提升 u 和 v**:
|
||||||
|
- 从 j = L-1(假设 L=4)到 j=0:
|
||||||
|
- j=3 到 j=2:fa[4][3] = 0, fa[5][3] = 0 → 相等,跳过
|
||||||
|
- j=1:
|
||||||
|
- fa[4][1] = 1
|
||||||
|
- fa[5][1] = 1
|
||||||
|
- 相等,跳过
|
||||||
|
- j=0:
|
||||||
|
- fa[4][0] = 2
|
||||||
|
- fa[5][0] = 3
|
||||||
|
- 不相等,提升:
|
||||||
|
- u = fa[4][0] = 2
|
||||||
|
- v = fa[5][0] = 3
|
||||||
|
|
||||||
|
7. **找到 LCA**:
|
||||||
|
- 最终,u = 2,v = 3
|
||||||
|
- fa[2][0] = 1
|
||||||
|
- fa[3][0] = 1
|
||||||
|
- LCA = 1
|
||||||
|
|
||||||
|
**总结**:LCA(7,5) = 1
|
||||||
|
|
||||||
|
#### 二进制位在代码中的应用
|
||||||
|
|
||||||
|
让我们回顾代码中 LCA 相关部分,并结合上述示例进一步理解。
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
ll lca(ll u, ll v){
|
||||||
|
if(dpt[u] < dpt[v]){
|
||||||
|
std::swap(u, v);
|
||||||
|
}
|
||||||
|
ll dif = dpt[u] - dpt[v];
|
||||||
|
// 将 u 提升到与 v 同一深度
|
||||||
|
for(ll j = 0; dif > 0; j++, dif >>= 1){
|
||||||
|
if(dif & 1){
|
||||||
|
u = fa[u][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(u == v){
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
// 同时提升 u 和 v 直到它们的父节点相同
|
||||||
|
for(ll j = L-1; j >= 0; j--){
|
||||||
|
if(fa[u][j] != fa[v][j]){
|
||||||
|
u = fa[u][j];
|
||||||
|
v = fa[v][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fa[u][0];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**详细解释**:
|
||||||
|
|
||||||
|
1. **提升 u 到 v 的深度**:
|
||||||
|
- 通过检查 dif 的每一位(即二进制位),决定是否需要进行 2^j 次提升。
|
||||||
|
- 如果 dif 的某一位为 1,则将 u 提升 2^j 步。
|
||||||
|
|
||||||
|
2. **同步提升 u 和 v**:
|
||||||
|
- 从最高的二进制位(L-1)开始,逐步检查并提升 u 和 v。
|
||||||
|
- 若 fa[u][j] != fa[v][j],则将 u 和 v 分别提升 2^j 步,直到它们接近 LCA。
|
||||||
|
|
||||||
|
3. **返回 LCA**:
|
||||||
|
- 最终,fa[u][0] 即为 LCA。
|
||||||
|
|
||||||
|
#### 二进制位与跳跃步数的对应关系
|
||||||
|
|
||||||
|
- 每一位 j(从 0 开始)对应着 2^j 的跳跃步数。
|
||||||
|
- 通过位操作,可以将任意步数的提升分解为若干个 2^j 的跳跃,从而高效实现多级提升。
|
||||||
|
|
||||||
|
**具体对应关系**:
|
||||||
|
|
||||||
|
| j(位数) | 2^j(跳跃步数) |
|
||||||
|
|----------|----------------|
|
||||||
|
| 0 | 1 |
|
||||||
|
| 1 | 2 |
|
||||||
|
| 2 | 4 |
|
||||||
|
| 3 | 8 |
|
||||||
|
| ... | ... |
|
||||||
|
| L-1 | 2^(L-1) |
|
||||||
|
|
||||||
|
**举例说明**:
|
||||||
|
|
||||||
|
假设需要提升一个节点 u 13 步:
|
||||||
|
|
||||||
|
- 13 的二进制表示为 1101。
|
||||||
|
- 这意味着需要:
|
||||||
|
- 2^0 = 1 步(j=0)
|
||||||
|
- 2^2 = 4 步(j=2)
|
||||||
|
- 2^3 = 8 步(j=3)
|
||||||
|
- 通过分别提升 u 一步、四步和八步,总共提升13步。
|
||||||
|
|
||||||
|
在代码中,这通过以下操作实现:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
for(ll j = 0; dif > 0; j++, dif >>= 1){
|
||||||
|
if(dif & 1){
|
||||||
|
u = fa[u][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 每次循环检查 dif 的最低位(dif & 1)。
|
||||||
|
- 若为1,则将 u 提升 2^j 步,即 u = fa[u][j]。
|
||||||
|
- 然后,将 dif 右移一位(dif >>= 1),准备检查下一位。
|
||||||
|
|
||||||
|
#### 为什么二进制提升高效?
|
||||||
|
|
||||||
|
- **时间复杂度**:计算 LCA 的时间复杂度为 O(log n),因为提升和同步提升均涉及对数级别的操作。
|
||||||
|
- **空间复杂度**:需要 O(n log n) 的空间来存储 fa 表,但这对于 n ≤ 5×10^5 是可接受的。
|
||||||
|
|
||||||
|
相比于传统的每次向上遍历节点的方式(可能达到 O(n) 的时间复杂度),二进制提升显著提高了效率,特别是在处理大规模数据时。
|
||||||
|
|
||||||
|
### 总结
|
||||||
|
|
||||||
|
在本题中,**二进制提升** 技术通过预计算每个节点的 2^j 祖先,结合二进制位的表示,能够在对数时间内高效地计算树中任意两个节点的 LCA。这种方法的核心在于利用二进制位对应的跳跃步数,实现快速的多级提升,从而优化查询效率。理解并掌握二进制提升对于解决大规模树问题(如本题)具有重要意义。
|
||||||
|
Loading…
Reference in New Issue
Block a user