LCA(Least Common Ancestors)最近公共祖先问题

参考:《挑战程序设计竞赛》、《算法艺术与信息学竞赛》学习指导

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


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值