蓝桥杯 2023 省赛 B 组 I 题 - 景区导游题解(LCA最近公共祖先)
蓝桥杯 2023 省赛 B 组 I 题 - 景区导游题解
题目分析
这道题目描述了一个树状结构的景区,景点之间通过摆渡车线路相连。我们需要计算在跳过游览路线中某一个景点后,整个游览路线所需的总时间。
解题思路
- 树结构处理:景点之间形成一棵树,我们需要高效地计算树上任意两点之间的距离。
- LCA(最近公共祖先):为了快速计算树上两点间距离,我们可以使用LCA技术。两点u和v之间的距离等于dist[u] + dist[v] - 2 * dist[lca(u,v)],其中dist[u]表示从根节点到u的距离。
- 预处理:使用广度优先搜索(BFS)预处理每个节点的深度、到根节点的距离以及倍增数组,用于快速查询LCA。
- 路线计算:对于原定游览路线,先计算完整路线总时间。然后对于每个可能跳过的景点,计算跳过后的路线时间:
- 跳过第一个景点:总时间减去第一个景点到第二个景点的时间
- 跳过最后一个景点:总时间减去倒数第二个景点到最后一个景点的时间
- 跳过中间某个景点:总时间减去前一个景点到当前景点的时间,减去当前景点到后一个景点的时间,再加上前一个景点直接到后一个景点的时间
代码实现
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 10, M = N * 2;
int n, m;
int arr[N];
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int f[N][16], depth[N];
int dist[N];
int q[M];
// 预处理每个节点的深度和倍增数组
void bfs()
{
depth[1] = 1; // 1是第一层源点
int tt = -1, hh = 0;
q[++tt] = 1;
while (hh <= tt)
{
auto u = q[hh++];
for (int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if (!depth[v])
{ // 该点未被处理
q[++tt] = v;
depth[v] = depth[u] + 1; // 深度 + 1
dist[v] = dist[u] + w[i];
f[v][0] = u; // u是当前节点的父节点
for (int k = 1; k <= 15; k++) // 当前节点倍增往上跳
f[v][k] = f[f[v][k - 1]][k - 1];
}
}
}
}
int lca(int a, int b)
{
// 将两个节点跳到同深度
if (depth[a] < depth[b])
swap(a, b); // 确保a在b的下方
for (int k = 15; k >= 0; k--)
if (depth[f[a][k]] >= depth[b])
a = f[a][k];
// 两个节点同时向上跳
if (a == b)
return a;
for (int k = 15; k >= 0; k--)
if (f[a][k] != f[b][k])
a = f[a][k], b = f[b][k];
// 返回两个节点的父节点
return f[a][0];
}
int getDis(int a, int b)
{
return dist[a] + dist[b] - 2 * dist[lca(a, b)];
}
void solve()
{
memset(h, -1, sizeof h);
cin >> n >> m;
for (int i = 1; i < n; i++)
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
bfs();
long long tot = 0; // 总时间
for (int i = 1; i <= m; i++)
cin >> arr[i];
// 计算完整路线总时间
for (int i = 1; i < m; i++)
tot += getDis(arr[i], arr[i + 1]);
// 计算跳过每个景点后的时间
for (int i = 1; i <= m; i++)
{
int ans = 0;
if (i == 1)
{ // 跳过第一个景点
ans = tot - getDis(arr[i], arr[i + 1]);
}
else if (i == m)
{ // 跳过最后一个景点
ans = tot - getDis(arr[i - 1], arr[i]);
}
else
{ // 跳过中间景点
ans = tot - getDis(arr[i - 1], arr[i]) - getDis(arr[i], arr[i + 1]) + getDis(arr[i - 1], arr[i + 1]);
}
cout << ans << " ";
}
}
signed main()
{
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
solve();
return 0;
}
复杂度分析
- 预处理阶段:BFS遍历整棵树,时间复杂度O(N)。每个节点处理16次倍增数组,总时间复杂度O(N log N)。
- 查询阶段:
- 计算完整路线时间:O(K)
- 每个查询处理:O(1)(因为已经预处理了LCA和距离)
- 总查询时间复杂度:O(K)
因此,算法的总时间复杂度为O(N log N + K),能够高效处理题目给出的数据规模。
总结
本题的关键在于利用LCA技术高效计算树上两点间距离,并通过预处理将查询时间优化到常数级别。这种方法适用于需要频繁查询树上路径问题的场景。