POJ - 1330 Nearest Common Ancestors(LCA最近公共祖先 朴素算法&倍增法)

A rooted tree is a well-known data structure in computer science and engineering. An example is shown below:
这里写图片描述

In the figure, each node is labeled with an integer from {1, 2,…,16}. Node 8 is the root of the tree. Node x is an ancestor of node y if node x is in the path between the root and node y. For example, node 4 is an ancestor of node 16. Node 10 is also an ancestor of node 16. As a matter of fact, nodes 8, 4, 10, and 16 are the ancestors of node 16. Remember that a node is an ancestor of itself. Nodes 8, 4, 6, and 7 are the ancestors of node 7. A node x is called a common ancestor of two different nodes y and z if node x is an ancestor of node y and an ancestor of node z. Thus, nodes 8 and 4 are the common ancestors of nodes 16 and 7. A node x is called the nearest common ancestor of nodes y and z if x is a common ancestor of y and z and nearest to y and z among their common ancestors. Hence, the nearest common ancestor of nodes 16 and 7 is node 4. Node 4 is nearer to nodes 16 and 7 than node 8 is.

For other examples, the nearest common ancestor of nodes 2 and 3 is node 10, the nearest common ancestor of nodes 6 and 13 is node 8, and the nearest common ancestor of nodes 4 and 12 is node 4. In the last example, if y is an ancestor of z, then the nearest common ancestor of y and z is y.

Write a program that finds the nearest common ancestor of two distinct nodes in a tree.

Input
The input consists of T test cases. The number of test cases (T) is given in the first line of the input file. Each test case starts with a line containing an integer N , the number of nodes in a tree, 2<=N<=10,000. The nodes are labeled with integers 1, 2,…, N. Each of the next N -1 lines contains a pair of integers that represent an edge –the first integer is the parent node of the second integer. Note that a tree with N nodes has exactly N - 1 edges. The last line of each test case contains two distinct integers whose nearest common ancestor is to be computed.
Output
Print exactly one line for each test case. The line should contain the integer that is the nearest common ancestor.
Sample Input
2
16
1 14
8 5
10 16
5 9
4 6
8 4
4 10
1 13
6 15
10 11
6 7
10 2
16 3
8 1
16 12
16 7
5
2 3
3 4
3 1
1 5
3 5
Sample Output
4
3

建树后查找最近公共祖先,LCA最近公共祖先朴素算法,首先查找两个要求节点的深度,如果深度不同,将较深的结点向上回溯寻找父亲知道和另一个节点相同深度,若统一深度后仍然不同父亲,则两个结点同时回溯,直到找到相同父亲。

#include<stdio.h>///LCA最近公共祖先查询,朴素算法
#include<string.h>
int fa[10008];
int deep(int x)///计算x节点深度
{
    int cnt=0;
    while(x)
    {
        cnt++;
        x=fa[x];
    }
    return cnt;
}
int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--)
    {
        memset(fa,0,sizeof(fa));///该数组记录每个节点的父亲,根节点父亲为0
        int s,f;
        scanf("%d",&n);
        for(int i=0;i<n-1;i++)
        {
            scanf("%d%d",&f,&s);
            fa[s]=f;
        }
        int a,b;
        scanf("%d%d",&a,&b);
        int aa=deep(a),bb=deep(b);
        if(aa<bb)///查询的深度若两个节点深度不同,将较深的节点先上移
        {
            int tt=bb-aa;
            while(tt--)
                b=fa[b];
        }
        else if(aa>bb)
        {
            int tt=aa-bb;
            while(tt--)
                a=fa[a];
        }
        while(a!=b)///两个节点深度相同时同时向上寻找父亲,直到父亲相同
            a=fa[a],b=fa[b];
        printf("%d\n",a);
    }
}

倍增法
首先预处理深度,利用预处理的信息,以log2n的复杂度向上回溯父亲

///LCA倍增法
#include<cstdio>
#include<cstring>
#include<vector>
#include<iostream>
using namespace std;
#define M(a,b) memset(a,b,sizeof(a))
#define pb push_back
int n;
int fa[15][10005],dep[10005];
vector<int>g[10005];
void dfs(int u)///深搜深度
{
    if (g[u].size()==0)///没有儿子的叶子结点直接返回
        return ;
    for (int i=0;i<g[u].size();++i)///遍历该结点所有儿子
    {
        int v=g[u][i];
        dep[v]=dep[u]+1;///儿子结点v的深度等于父亲的深度+1
        dfs(v);///继续查找儿子的儿子的深度
    }
}
void init()///预处理倍增数组
{
    for (int i=0;i<13;++i)///某个结点向上2的13次幂的祖先记录
        for (int j=1;j<=n;++j)///遍历所有结点
        {
            if (fa[i][j]==-1) continue;///根节点跳过
            fa[i+1][j]=fa[i][ fa[i][j] ];///倍增,2的i+1次幂的结点j的父亲,等于2的i次幂的结点j的父亲的父亲
        }
}
int lca(int u,int v)
{
    if (dep[u]>dep[v]) swap(u,v);///规定v的深度是大于u深度的结点
    for (int i=13;i>=0;--i)///若两结点深度不同
        if ((dep[v]-dep[u])&(1<<i))///根据深度之差的二进制,判断第i位是0还是1,是1则向上跳
            v=fa[i][v];

    if (u==v)///统一深度之后若两结点相同,则直接返回该结点名,表示其中那个深度低的是公共祖先
        return u;

    for (int i=13;i>=0;--i)///两结点同时上跳回溯
        if (fa[i][u]!=fa[i][v])
        {
            u=fa[i][u];
            v=fa[i][v];
        }
    return fa[0][u];
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for (int i=1;i<=n;++i) g[i].clear();
        M(fa,-1);
        M(dep,0);
        for (int i=1;i<n;++i)///建树
        {
            int u,v;
            scanf("%d%d",&u,&v);
            fa[0][v]=u;///记录每个节点v的直接父亲u
            g[u].pb(v);///在u的直系儿子中增加结点v
        }
        int rt;
        for (int i=1;i<=n;++i)///找根节点
            if (fa[0][i]==-1)
            {
                rt=i;
                break;
            }
        dep[rt]=1;///根结点上深度定为1
        dfs(rt);///深搜所有结点
        init();
        int u,v;
        scanf("%d%d",&u,&v);
        printf("%d\n",lca(u,v));
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值