题目链接
题意
n个村子,被n-1个条边连接,每次询问u和v的最近距离。
解析
- 求u和v的到他们的最近公共祖先的距离即可。
- 基本算法
- 在线算法 倍增法
每次询问O(logN)
d[i] 表示 i节点的深度, p[j,i] 表示 i 的 2^j 倍祖先
那么就有一个递推式子 p[j,i]=p[p[j,i-1],i-1]
这样子一个O(NlogN)的预处理求出每个节点的 2^k 的祖先
然后对于每一个询问的点对a, b的最近公共祖先就是:
先判断是否 d[a] > d[b] ,如果是的话就交换一下(保证 a 的深度小于 b 方便下面的操作)然后把b 调到与a 同深度, 同深度以后再把a, b 同时往上调(dec(j)) 调到有一个最小的j 满足p[j,a]!=p[j,b] (a b 是在不断更新的), 最后再把 a, b 往上调 (a=p[0,a], b=p[0,b]) 一个一个向上调直到a = b, 这时 a or b 就是他们的最近公共祖先
- 在线算法 倍增法
相关知识
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn = 40000+100;
typedef long long LL;
int parent[20][maxn], dis[maxn];
int dep[maxn];
vector<pair<int, int> >g[maxn];
void dfs(int u, int p, int d, int D) {
parent[0][u] = p;
dep[u] = d;
for (int i=0; i<g[u].size(); i++) {
if (g[u][i].first != p)
dfs(g[u][i].first, u, d+1, D+g[u][i].second);
}
dis[u] = D;
}
void init(int V) {
dfs(0, -1, 0, 0);
for (int k=0; k+1 < 20; k++) {
for (int v=0; v<V; v++) {
if (parent[k][v] < 0)
parent[k+1][v] = -1;
else
parent[k+1][v] = parent[k][parent[k][v]];
}
}
}
int lca(int u, int v) {
if (dep[u] > dep[v])
swap(u, v);
for (int i=0; i<20; i++)
if ((dep[v]-dep[u]) >> i & 1)
v = parent[i][v];
if (u == v)
return u;
for (int j=20-1; j>=0; j--) {
if (parent[j][u] != parent[j][v]) {
u = parent[j][u];
v = parent[j][v];
}
}
return parent[0][u];
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
int n, m;
memset(dis, 0, sizeof(dis));
scanf("%d%d", &n, &m);
for (int i=0; i<n-1; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
g[u-1].push_back(make_pair(v-1, w));
g[v-1].push_back(make_pair(u-1, w));
}
init(n);
for (int i=0; i<m; i++) {
int u, v;
scanf("%d%d", &u, &v);
int p = lca(u-1, v-1);
printf("%d\n", dis[u-1]+dis[v-1]-2*dis[p]);
}
}
return 0;
}