参考:《挑战程序设计竞赛》、《算法艺术与信息学竞赛》学习指导
http://www.cnblogs.com/scau20110726/archive/2013/06/14/3135095.html
一、
树的最近公共祖先(Lowest Common Ancestor)问题是树结构上最经典的问题之一
给一棵树T,每个询问形如: “点u和点v 的公共祖先是哪个点? ”,此问题的答案被记为 LCA(T, u, v)
LCA 问题的算法分为在线和离线两种,前者要求在回答后一个问题之前必须给出前一个问题的输出
而离线问题允许在读入所有询问之后一次性给出所有问题的答案
LCA 问题的应用很多,例如它可以用来回答这样的询问“点 u 和点 v 的距离是多少? ”
由于在树中两点的简单路是唯一的,所以这个距离等于 u 到 LCA(T, u, v) 再到 v 的距离,关键仍然是 LCA
二、在线 LCA 的算法
1、基于 RMQ 的算法
对于涉及有根树的问题,将树转为从根 dfs 标号后得到的序列处理的技巧常常十分有效
对于 LCA,利用该技巧也能够高效的计算
给树 T 做 dfs,并记录下每次到达的结点
第一个记录的结点是 root(T),每经过一条边都记录它的端点。由于每条边恰好经过了两次,因此一共将记录 2n - 1 个结点
我们用 num[1,...,2n - 1] 来表示这个数组,并用 pos[i] 来表示 num 数组中第一个值为 i 的元素下标,那么对于任何 po[u] < pos[v] 的结点 u,v 来说
dfs 从第一次访问 u 到第一次访问 v 所经过的路径应该是 num[pos[u],...,pos[v]]
虽然这些结点会包含 u 的后代,但是其中深度最小的结点一定是 u 和 v 的 LCA
即:令数组 depth[i] 表示结点 num[i] 的深度,那么当 pos[u] ≤ pos[v] 时,LCA(T, u, v) = RMQ(depth, pos[u], pos[v])
类似地,如果 pos[u] > pos[v],LCA(T, u, v) = RMQ(depth, pos[v], pos[u])
这样,在 O(n) 时间内把 LCA 问题转化为了 RMQ 问题
HDU 2586 How far away ?
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#include <cmath>
#include <cctype>
#include <bitset>
#include <ctime>
using namespace std;
#define REP(i, n) for (int i = 0; i < (n); ++i)
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef pair<int, int> Pair;
const ull mod = 1e9 + 7;
const int INF = 0x7fffffff;
const int maxn = 4e4 + 10;
int num[maxn<<1], pos[maxn], depth[maxn<<1], head[maxn], dp[maxn<<1][30], ans[maxn];
bool vis[maxn];
int Count = 0, Edge_Count = 0;
int n, m;
struct Edge {
int v, val, next;
};
Edge edge[maxn<<1];
void add(int u, int v, int val);
void dfs(int u, int d);
void ST(int len);
int RMQ(int low, int high);
int LCA(int u, int v);
int main()
{
#ifdef __AiR_H
freopen("in.txt", "r", stdin);
#endif // __AiR_H
int T;
scanf("%d", &T);
while (T--) {
int u, v, val;
scanf("%d %d", &n, &m);
Edge_Count = 0;
memset(head, -1, sizeof(head));
memset(vis, false, sizeof(vis));
for (int i = 0; i < n-1; ++i) {
scanf("%d %d %d", &u, &v, &val);
add(u, v, val);
add(v, u, val);
}
Count = 0;
ans[1] = 0;
dfs(1, 1);
ST(2*n-1);
while (m--) {
scanf("%d %d", &u, &v);
int lca = LCA(u, v);
printf("%d\n", ans[u] + ans[v] - 2*ans[lca]);
}
}
return 0;
}
void add(int u, int v, int val)
{
edge[Edge_Count] = Edge{v, val, head[u]};
head[u] = Edge_Count++;
}
void dfs(int u, int d)
{
vis[u] = true;
num[++Count] = u;
pos[u] = Count;
depth[Count] = d;
for (int i = head[u]; i != -1; i = edge[i].next) {
int v = edge[i].v;
if (!vis[v]) {
ans[v] = ans[u] + edge[i].val;
dfs(v, d+1);
num[++Count] = u;
depth[Count] = d;
}
}
}
void ST(int len)
{
for (int i = 1; i <= len; ++i) {
dp[i][0] = i;
}
for (int j = 1; (1<<j) <= len; ++j) {
for (int i = 1; i + (1<<j) - 1 <= len; ++i) {
int a = dp[i][j-1], b = dp[i+(1<<(j-1))][j-1];
dp[i][j] = depth[a] < depth[b] ? a : b;
}
}
}
int RMQ(int low, int high)
{
int k = 0;
while ((1<<(k+1)) <= high-low+1) {
++k;
}
int a = dp[low][k], b = dp[high-(1<<k)+1][k];
return depth[a] < depth[b] ? a : b;
}
int LCA(int u, int v)
{
int x = pos[u], y = pos[v];
if (x > y) {
swap(x, y);
}
int ret = RMQ(x, y);
return num[ret];
}
2、基于倍增的做法
参考:http://blog.csdn.net/jarjingx/article/details/8183240
HDU 2586 How far away ?
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#include <cmath>
#include <cctype>
#include <bitset>
#include <ctime>
using namespace std;
#define REP(i, n) for (int i = 0; i < (n); ++i)
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef pair<int, int> Pair;
const ull mod = 1e9 + 7;
const int INF = 0x7fffffff;
const int maxn = 4e4 + 10;
int pre[maxn][30], depth[maxn], head[maxn], dis[maxn];
bool vis[maxn];
int Edge_Count = 0;
int n, m;
struct Edge {
int v, val, next;
};
Edge edge[maxn<<1];
void add(int u, int v, int val);
void dfs(int u, int _depth, int _pre);
void Init(void);
int LCA(int u, int v);
int main()
{
#ifdef __AiR_H
freopen("in.txt", "r", stdin);
#endif // __AiR_H
int T;
scanf("%d", &T);
while (T--) {
int u, v, val;
scanf("%d %d", &n, &m);
Edge_Count = 0;
memset(head, -1, sizeof(head));
memset(vis, false, sizeof(vis));
for (int i = 0; i < n-1; ++i) {
scanf("%d %d %d", &u, &v, &val);
add(u, v, val);
add(v, u, val);
}
memset(pre, -1, sizeof(pre));
dfs(1, 0, -1);
Init();
while (m--) {
scanf("%d %d", &u, &v);
int lca = LCA(u, v);
printf("%d\n", dis[u] + dis[v] - 2*dis[lca]);
}
}
return 0;
}
void add(int u, int v, int val)
{
edge[Edge_Count] = Edge{v, val, head[u]};
head[u] = Edge_Count++;
}
void dfs(int u, int _depth, int _pre)
{
vis[u] = true;
depth[u] = _depth;
pre[u][0] = _pre;
for (int i = head[u]; i != -1; i = edge[i].next) {
int v = edge[i].v;
if (!vis[v]) {
dis[v] = dis[u] + edge[i].val;
dfs(v, _depth+1, u);
}
}
}
void Init(void)
{
for (int j = 1; (1<<j) <= n; ++j) {
for (int i = 1; i <= n; ++i) {
if (pre[i][j-1] != -1) {
pre[i][j] = pre[pre[i][j-1]][j-1];
}
}
}
}
int LCA(int u, int v)
{
int t = 0;
if (depth[u] != depth[v]) {
if (depth[u] < depth[v]) {
swap(u, v);
}
while ((1<<t) <= depth[u]) {
++t;
}
--t;
for (int i = t; i >= 0; --i) {
if (depth[u] - (1<<i) >= depth[v]) {
u = pre[u][i];
}
}
}
if (u == v) {
return u;
}
for (int i = t; i >= 0; --i) {
if (pre[u][i] != -1 && pre[u][i] != pre[v][i]) {
u = pre[u][i], v = pre[v][i];
}
}
return pre[u][0];
}
二、离线 LCA 的 Tarjan 算法
参考:http://blog.csdn.net/jarjingx/article/details/8183240
http://www.cnblogs.com/scau20110726/archive/2013/05/26/3100265.html
HDU 2586 How far away ?
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#include <cmath>
#include <cctype>
#include <bitset>
#include <ctime>
using namespace std;
#define REP(i, n) for (int i = 0; i < (n); ++i)
typedef long long ll;
typedef unsigned long long ull;
typedef unsigned int uint;
typedef pair<int, int> Pair;
const ull mod = 1e9 + 7;
const int INF = 0x7fffffff;
const int maxn = 4e4 + 10;
int pre[maxn], ancestor[maxn], head[maxn], head_ask[maxn], dis[maxn];
bool vis[maxn];
int Edge_Count = 0, Ask_Count = 0;
int n, m;
struct Edge {
int v, val, next;
};
struct Ask {
int u, v, lca, next;
};
Edge edge[maxn<<1];
Ask ask[maxn];
void add_edge(int u, int v, int val);
void add_ask(int u, int v);
int Find(int x);
void Union(int u, int v);
void Tarjan(int u);
int main()
{
#ifdef __AiR_H
freopen("in.txt", "r", stdin);
#endif // __AiR_H
int T;
scanf("%d", &T);
while (T--) {
int u, v, val;
scanf("%d %d", &n, &m);
memset(head, -1, sizeof(head));
memset(head_ask, -1, sizeof(head_ask));
Edge_Count = Ask_Count = 0;
for (int i = 0; i < n-1; ++i) {
scanf("%d %d %d", &u, &v, &val);
add_edge(u, v, val);
add_edge(v, u, val);
}
for (int i = 0; i < m; ++i) {
scanf("%d %d", &u, &v);
add_ask(u, v);
add_ask(v, u);
}
memset(vis, false, sizeof(vis));
dis[1] = 0;
Tarjan(1);
for (int i = 0; i < m; ++i) {
int t = i*2, u = ask[t].u, v = ask[t].v, lca = ask[t].lca;
printf("%d\n", dis[u] + dis[v] - 2*dis[lca]);
}
}
return 0;
}
void add_edge(int u, int v, int val)
{
edge[Edge_Count] = Edge{v, val, head[u]};
head[u] = Edge_Count++;
}
void add_ask(int u, int v)
{
ask[Ask_Count] = Ask{u, v, -1, head_ask[u]};
head_ask[u] = Ask_Count++;
}
int Find(int x)
{
return x == pre[x] ? x : pre[x] = Find(pre[x]);
}
void Union(int u, int v)
{
pre[v] = pre[u];
}
void Tarjan(int u)
{
vis[u] = true;
ancestor[u] = pre[u] = u;
for (int i = head[u]; i != -1; i = edge[i].next) {
int v = edge[i].v;
if (!vis[v]) {
dis[v] = dis[u] + edge[i].val;
Tarjan(v);
Union(u, v);
}
}
for (int i = head_ask[u]; i != -1; i = ask[i].next) {
int v = edge[i].v;
if (vis[v]) {
ask[i].lca = ask[i^1].lca = ancestor[Find(v)];
}
}
}
题集:http://www.cnblogs.com/scau20110726/archive/2013/06/14/3135095.html