一、题目
1、原题链接
2、题目描述
给定一个 n 个结点(编号 1∼n)构成的二叉树,其根结点为 1 号点。
进行 m 次询问,每次询问两个结点之间的最短路径长度。
树中所有边长均为 1。
输入格式
第一行包含一个整数 T,表示共有 T 组测试数据。
每组数据第一行包含两个整数 n,m。
接下来 n 行,每行包含两个整数,其中第 i 行的整数表示结点 i 的子结点编号。如果没有子结点则输出 −1。
接下来 m 行,每行包含两个整数,表示要询问的两个结点的编号。
输出格式
每组测试数据输出 m 行,代表查询的两个结点之间的最短路径长度。
数据范围
1≤T≤10,1≤n,m≤1000
输入样例:
1 8 4 2 3 4 5 6 -1 -1 -1 -1 7 -1 -1 8 -1 -1 -1 1 6 4 6 4 5 8 1
输出样例:
2 4 2 4
二、解题报告
1、思路分析
思路来源:y总讲解视频
y总yyds
(1)可以将题目所求的两点之间的最短路径长度转化为两点距离其公共祖先的距离和。
(2)我们可以计算出所求两点距离根结点的距离d[x1]
和d[x2]
,然后再求出其最近公共祖先距离根结点的距离d[x3]
,则两点之间的最短长度为d[x1]+d[x2]-2*d[x3]
。
(3)而上述距离可以利用深搜来求,最近公共祖先可以利用爬山法:先将深度较深的点往上爬,爬到与另一个点的深度相同后,两点一起往上爬,爬到的第一个相同的点即为最近公共祖先。
(4)模拟上述过程,求解即可。
2、时间复杂度
时间复杂度为O(n*m)
3、代码详解
#include <iostream>
#include <cstring>
using namespace std;
const int N=1010;
int l[N],r[N],p[N]; //l[],r[]存储每个结点的左右儿子,p[]存储每个结点的父结点
int dist[N]; //dist[]存储每个结点到根结点的距离
int T,n,m;
//dfs求每个点距离根结点的距离
void dfs(int u,int d){ //u代表当前点编号,d代表距离
dist[u]=d;
if(l[u]!=-1) dfs(l[u],d+1); //如果左儿子存在,继续从左儿子向下延伸
if(r[u]!=-1) dfs(r[u],d+1); //如果右儿子存在,继续从右儿子向下延伸
}
//爬山法求最近公共祖先
int getLca(int x,int y){
if(dist[x]>dist[y]) swap(x,y); //始终保持y的深度比x大
while(dist[y]>dist[x]) y=p[y]; //y向上爬到与x同一深度
while(y!=x) x=p[x],y=p[y]; //x,y一起向上爬,直到遇到第一个公共祖先
return x;
}
int main(){
cin>>T;
while(T--){
cin>>n>>m;
memset(l,-1,sizeof l);
memset(r,-1,sizeof r);
for(int i=1;i<=n;i++){
int lc,rc;
cin>>lc>>rc;
l[i]=lc,r[i]=rc;
if(lc!=-1) p[lc]=i;
if(rc!=-1) p[rc]=i;
}
dfs(1,0);
while(m--){
int x,y;
cin>>x>>y;
int lca=getLca(x,y);
int ans=dist[x]+dist[y]-2*dist[lca];
cout<<ans<<endl;
}
}
return 0;
}
三、知识风暴
最近公共祖先
- 可以利用爬山法进行求解:先将位置较低的点往上爬,爬到与另一个点高度一致,然后两个点一起向上爬,直到遇到第一个公共祖先为止(即到达的点相同)。