题目 : http://www.gdfzoj.com/oj/contest/105/problems/3
题意很简单,就是给了一棵树,求任意两点最近公共祖先(LCA)
不用过多分析了
直接解释倍增算法
首先,选定一个根,搜索出整棵树的深度
- dfs 比 bfs 要好写很多
然后,预处理出 f 数组
f[i][j] 表示节点 i 的第 2^j 个祖先
满足递推式:f[i][j] = f [f[i][j-1]] [j-1]
接下来,对于每一次查询
先把两个节点移动到同一深度上,判断
再不断尝试向上移动,同时判断
很简单地搞定了LCA问题
细节看代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 40500;
struct Edge{
int u, v, w;
Edge(int u=0, int v=0, int w=0) : u(u), v(v), w(w) {}
} e[maxn*2];
vector<int> G[maxn];
int f[maxn][22], cost[maxn][22], dep[maxn];
int tot = 0, n, q;
void add_edge(int u, int v, int w) {
e[++tot] = (Edge){u, v, w};
G[u].push_back(tot);
e[++tot] = (Edge){v, u, w};
G[v].push_back(tot);
}
void dfs(int u) {
for(int i = 0; i < G[u].size(); i++) {
int v = e[G[u][i]].v, w = e[G[u][i]].w;
if(dep[v]) continue;
dep[v] = dep[u]+1;
f[v][0] = u;
cost[v][0] = w;
dfs(v);
}
}
void init() {
for(int j = 1; (1<<j) <= n; j++)
for(int i = 1; i <= n; i++)
if(f[i][j-1]) {
f[i][j] = f[f[i][j-1]][j-1];
cost[i][j] = cost[f[i][j-1]][j-1] + cost[i][j-1];
}
}
int lca(int a, int b) {
int d, ans = 0;
if(dep[a] < dep[b]) swap(a, b);
for(d = 0; (1<<d) <= dep[a]; d++); d--;
for(int i = d; i >= 0; i--) if(dep[f[a][i]] >= dep[b])
ans += cost[a][i], a = f[a][i];
if(a == b) return ans;
for(int i = d; i >= 0; i--) if(f[a][i] != f[b][i])
ans += cost[a][i]+cost[b][i], a = f[a][i], b = f[b][i];
return ans + cost[a][0] + cost[b][0];
}
int main() {
scanf("%d%d", &n, &q);
for(int i = 1; i < n; i++) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
add_edge(u ,v ,w);
}
memset(dep, 0, sizeof(dep));
memset(f, 0, sizeof(f));
dep[1] = 1;
dfs(1);
init();
while(q--) {
int a, b;
scanf("%d%d", &a, &b);
printf("%d\n", lca(a, b));
}
return 0;
}