P9245 [蓝桥杯 2023 省 B] 景区导游 题解
题目解析
本题要求在给定的树结构和一个节点访问序列中,对于每个节点,计算跳过该节点后的路径总长度。关键在于高效计算树上任意两点间距离,并利用预处理优化计算过程。
方法思路
- 树结构预处理
DFS 计算节点到根的距离:通过遍历树,记录每个节点到根节点的距离,存储于 dist 数组。
倍增法预处理 LCA:使用二进制跳跃法预处理每个节点的祖先信息,以便快速查询任意两节点的最近公共祖先。
- 路径距离计算
任意两节点 u 和 v 的距离公式:
dist(u, v) = dist[u] + dist[v] - 2 * dist[LCA(u, v)]
预处理原路径总长度:遍历访问序列,累加每对相邻节点的距离。
- 高效处理每个跳过点
端点处理:若跳过首节点或尾节点,直接减去对应的单段距离。
中间点处理:减去原两段距离,并加上新段距离,避免重复计算。
解决方案
算法:倍增求最近公共祖先
时间复杂度:
- 初始化 O(n log n)
- 求最短距离 O(m log n)
C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=1e5+10,M=N*2;
// 链式前向星存储图的相关数组
int e[M],ne[M],h[N],idx;
ll w[M];
// 倍增求LCA所需数组
int depth[N],fa[N][20];//log(1e5)约等于17,开到20
int q[N];
int n,m;
ll dist[N];// 从根节点到每个节点的距离数组
int order[N];
void add(int a,int b,ll c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
// 深度优先搜索函数,用于计算从根节点到每个节点的距离
void dfs(int u,int fa){
for(int i=h[u];~i;i=ne[i]){// 遍历当前节点 u 的所有邻接节点
int j=e[i];
if(j!=fa){// 如果邻接节点 j 不是当前节点 u 的父节点
dist[j]=dist[u]+w[i];// 计算邻接节点 j 到根节点的距离
dfs(j,u);// 递归调用 dfs 函数,继续搜索邻接节点 j
}
}
}
// 广度优先搜索函数,用于计算每个节点的深度和祖先信息
void bfs(int root){
memset(depth,0x3f,sizeof depth);
depth[0]=0,depth[root]=1;// 虚拟节点 0 的深度为 0,根节点的深度为 1
int hh=0,tt=1;
q[0]=root;
while(hh<tt){
int u=q[hh++];
for(int i=h[u];~i;i=ne[i]){
int v=e[i];
if(depth[v]>depth[u]+1){
depth[v]=depth[u]+1;// 如果邻接节点 v 的深度大于当前节点 u 的深度加 1就更新邻接节点 v 的深度
q[tt++]=v;
fa[v][0]=u;// 记录邻接节点 v 的父节点
for(int k=1;k<=17;k++) fa[v][k]=fa[fa[v][k-1]][k-1];// 预处理祖先信息
}
}
}
}
// 计算两个节点 a 和 b 的最近公共祖先
int lca(int a,int b){
if(depth[a]<depth[b]) swap(a,b);// 确保 a 的深度不小于 b 的深度
for(int k=17;k>=0;k--) if(depth[fa[a][k]]>=depth[b]) a=fa[a][k];// 让 a 跳到与 b 同一深度
if(a==b) return a;// 如果 a 和 b 相等,说明 a 就是最近公共祖先
// 同时向上跳,直到找到最近公共祖先
for(int k=17;k>=0;k--){
if(fa[a][k]!=fa[b][k]){
a=fa[a][k],b=fa[b][k];
}
}
return fa[a][0];
}
// 计算两个节点 a 和 b 之间的距离
ll get_dist(int a,int b){
return dist[a]+dist[b]-2*dist[lca(a,b)];
}
int main(){
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);// 初始化链式前向星头数组为 -1
for(int i=0,a,b;i<n-1;i++){
long long c;
scanf("%d%d%lld",&a,&b,&c);
add(a,b,c),add(b,a,c);// 添加无向边
}
int root=1;
dist[root]=0;// 初始化根节点到自身的距离为 0
dfs(root,-1);// 调用 dfs 函数计算到树根的距离
bfs(root);// 调用 bfs 函数计算深度和祖先信息
for(int i=1;i<=m;i++) scanf("%d",&order[i]);
ll original=0;
for(int i=1;i<=m-1;i++) original+=get_dist(order[i],order[i+1]);// 计算原始路径的总距离
// 遍历每个跳过的点
for(int i=1;i<=m;i++){
ll ans=0;
if(i==1) ans=original-get_dist(order[1],order[2]);// 如果是第一个查询点
else if(i==m) ans=original-get_dist(order[m-1],order[m]);// 如果是最后一个查询点
else{
ll part1=get_dist(order[i-1],order[i]),part2=get_dist(order[i],order[i+1]);// 计算删除该点前的两段路径长度
ll new_part=get_dist(order[i-1],order[i+1]);// 计算删除该点后的新路径长度
ans=original-part1-part2+new_part;// 计算删除该点后的总距离
}
printf("%lld ",ans);
}
return 0;
}
代码解释
- 数据结构与输入处理
使用邻接表存储树结构,add 函数处理双向边。
读取节点访问序列并存储于 order 数组。
- 预处理阶段
DFS:从根节点出发,计算每个节点到根的距离。
BFS:构建每个节点的深度和倍增表,用于快速查询最近公共祖先。
- 距离计算
get_dist 函数利用 LCA 计算两点间距离,公式为两节点到根的距离减去两倍 LCA 到根的距离。
- 路径总和优化
预处理原路径总长度后,针对每个跳过点,通过 \(O(1)\) 时间复杂度计算新路径长度,避免重复遍历。
后记
十年OI一场空,不开long long见祖宗!

浙公网安备 33010602011771号