LCA - 距离 - AcWing 1171
给出 n 个点的一棵树,多次询问两点之间的最短距离。
注意:
- 边是无向的。
- 所有节点的编号是 1,2,…,n。
输入格式
第一行为两个整数 n 和 m。n 表示点数,m 表示询问次数;
下来 n−1 行,每行三个整数 x,y,k,表示点 x 和点 y 之间存在一条边长度为 k;
再接下来 m 行,每行两个整数 x,y,表示询问点 x 到点 y 的最短距离。
树中结点编号从 1 到 n。
输出格式
共 m 行,对于每次询问,输出一行询问结果。
数据范围
2 ≤ n ≤ 1 0 4 , 1 ≤ m ≤ 2 × 1 0 4 , 0 < k ≤ 100 , 1 ≤ x , y ≤ n 2≤n≤10^4, 1≤m≤2×10^4, 0<k≤100, 1≤x,y≤n 2≤n≤104,1≤m≤2×104,0<k≤100,1≤x,y≤n
输入样例1:
2 2
1 2 100
1 2
2 1
输出样例1:
100
100
输入样例2:
3 2
1 2 10
3 1 15
1 2
3 2
输出样例2:
10
25
分析:
求 树 上 任 意 两 点 a 和 b 的 距 离 : 求树上任意两点a和b的距离: 求树上任意两点a和b的距离:
设 a 和 b 的 最 近 公 共 祖 先 为 p , d i s [ i ] 表 示 点 i 到 根 节 点 的 距 离 。 设a和b的最近公共祖先为p,dis[i]表示点i到根节点的距离。 设a和b的最近公共祖先为p,dis[i]表示点i到根节点的距离。
则 a 和 b 的 距 离 d a − > b = d i s [ a ] + d i s [ b ] − 2 × d i s [ p ] 。 作 图 容 易 发 现 其 正 确 性 。 则a和b的距离d_{a->b}=dis[a]+dis[b]-2×dis[p]。作图容易发现其正确性。 则a和b的距离da−>b=dis[a]+dis[b]−2×dis[p]。作图容易发现其正确性。
t a r j a n 算 法 求 L C A , 时 间 复 杂 度 O ( n + m ) 。 tarjan算法求LCA,时间复杂度O(n+m)。 tarjan算法求LCA,时间复杂度O(n+m)。
步 骤 : 步骤: 步骤:
将 整 个 数 分 为 三 个 部 分 : 将整个数分为三个部分: 将整个数分为三个部分:
① 、 已 经 遍 历 过 的 分 支 。 ①、已经遍历过的分支。 ①、已经遍历过的分支。
② 、 正 在 遍 历 的 分 支 。 ②、正在遍历的分支。 ②、正在遍历的分支。
③ 、 未 遍 历 的 分 支 。 ③、未遍历的分支。 ③、未遍历的分支。
当 我 们 从 左 到 右 依 次 遍 历 每 个 分 支 时 , 当我们从左到右依次遍历每个分支时, 当我们从左到右依次遍历每个分支时,
已 遍 历 的 分 支 中 的 点 和 正 在 遍 历 的 分 支 中 的 点 的 最 近 公 共 祖 先 就 处 于 正 在 遍 历 的 分 支 上 。 已遍历的分支中的点和正在遍历的分支中的点的最近公共祖先就处于正在遍历的分支上。 已遍历的分支中的点和正在遍历的分支中的点的最近公共祖先就处于正在遍历的分支上。
如
上
图
,
橙
色
区
域
为
已
遍
历
分
支
,
红
色
区
域
为
正
在
遍
历
分
支
,
绿
色
区
域
为
未
遍
历
分
支
。
如上图,橙色区域为已遍历分支,红色区域为正在遍历分支,绿色区域为未遍历分支。
如上图,橙色区域为已遍历分支,红色区域为正在遍历分支,绿色区域为未遍历分支。
橙 色 区 域 内 的 点 与 红 色 区 域 内 点 的 最 近 公 共 祖 先 均 处 于 正 在 遍 历 的 红 色 区 域 内 。 橙色区域内的点与红色区域内点的最近公共祖先均处于正在遍历的红色区域内。 橙色区域内的点与红色区域内点的最近公共祖先均处于正在遍历的红色区域内。
所 以 我 们 先 将 正 在 遍 历 的 点 标 记 , 然 后 递 归 遍 历 到 当 前 区 域 的 底 部 , 所以我们先将正在遍历的点标记,然后递归遍历到当前区域的底部, 所以我们先将正在遍历的点标记,然后递归遍历到当前区域的底部,
再 将 已 遍 历 的 区 域 内 的 点 通 过 并 查 集 合 并 到 对 应 的 根 节 点 上 。 再将已遍历的区域内的点通过并查集合并到对应的根节点上。 再将已遍历的区域内的点通过并查集合并到对应的根节点上。
接 着 根 据 询 问 , 更 新 对 应 的 两 个 点 之 间 的 距 离 。 接着根据询问,更新对应的两个点之间的距离。 接着根据询问,更新对应的两个点之间的距离。
预处理:
预 处 理 每 个 点 到 根 节 点 的 距 离 。 预处理每个点到根节点的距离。 预处理每个点到根节点的距离。
d f s 先 加 边 再 递 归 即 可 。 dfs先加边再递归即可。 dfs先加边再递归即可。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define P pair<int,int>
using namespace std;
const int N=10010, M=N*2;
int n,m;
int e[M],w[M],ne[M],h[N],idx;
int dis[N];
int p[N];
int st[N],res[M];
vector<P> query[M];
void add(int a,int b,int 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])
{
int j=e[i];
if(j==fa) continue;
dis[j]=dis[u]+w[i];
dfs(j,u);
}
}
int Find(int x)
{
if(p[x]!=x) p[x]=Find(p[x]);
return p[x];
}
void tarjan(int u)
{
st[u]=1; //正在遍历u
for(int i=h[u];~i;i=ne[i]) //合并
{
int j=e[i];
if(!st[j]) //未遍历过
{
tarjan(j);
p[j]=u;
}
}
for(auto t : query[u])
{
int v=t.first,id=t.second;
if(st[v]==2) res[id]=dis[u]+dis[v]-2*dis[Find(v)];
}
st[u]=2; //遍历过
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
int a,b,c;
for(int i=0;i<n-1;i++)
{
scanf("%d%d%d",&a,&b,&c);
add(a,b,c),add(b,a,c);
}
for(int i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
if(a!=b)
{
query[a].push_back({b,i});
query[b].push_back({a,i});
}
}
for(int i=1;i<=n;i++) p[i]=i;
dfs(1,-1);
tarjan(1);
for(int i=0;i<m;i++) printf("%d\n",res[i]);
return 0;
}