poj1330 Nearest Common Ancestors(LCA离线算法)


http://poj.org/problem?id=1330

题意:给你n个节点和n-1个关系,通过关系建起一棵树,然后给你两个节点,求他们的最小公共祖先。


ps:这里的Tarjan离线算法貌似和前些日子做过的用Tarjan判断强连通不一样,难道Tarjan发明的算法都叫Tarjan算法么,那么他发明了应该不止一种的话。。。细思恐极


思路:这题是树和并查集的完美结合,边信息用头插法保存。首先说下LCA离线算法思想(复制别人的):

“离线算法需要预先将所有的询问存起来,等到dfs的时候一下逐一输出结果。
dfs对树进行遍历,当某个节点u的一个子树全部遍历完以后,就更新这棵子树的节点的父节点为u(转化为并查集:将该子树集合加入到u所在的集合中,并更新子树集合的父亲为u)如果当前节点x是被询问到的节点,并且一直相关的(询问了x,y的最近公共祖先)另一个节y点已经搜索过,则x与y的最近公共祖先为y此时的父亲。”

结合此博客的图,我们可以看出只要遍历过的未得到结果的子树,都会变为集合中的一部分,而集合中元素都以该集合为子树的根为根节点。于是巧妙的地方就出现了,这些子树所指向的根节点,一直都在查找的关键路径上!模拟一下就会发现,这个树是会变化的,就好像树在开花一样= =,(我把这里变为集合的操作称为开花,是不是有点像大笑),这样叶子都聚集在关键路径上,一旦查找到当前元素且对应元素已被访问,那对应元素的根节点即为最小公共祖先。



#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>

using namespace std;

typedef __int64 LL;

const int N = 100010;
const int INF = 0x3f3f3f3f;

int pre[N], in[N], head[N], pos, x, y, n;
bool vis[N];

struct node
{
    int from, to, next;
}edge[N];

void add(int u, int v)
{
    edge[pos] = (struct node){u, v, head[u]};
    head[u] = pos++;
}

int Find(int x)
{
    if(x == pre[x]) return x;
    pre[x] = Find(pre[x]);
    return pre[x];
}

void LCA(int u)
{
    pre[u] = u;
    for(int i = head[u]; i != -1; i = edge[i].next)
    {
        LCA(edge[i].to);
        pre[edge[i].to] = u;
    }
    vis[u] = true;
    if(u == x && vis[y])
    {
        printf("%d\n", Find(y));
        return;
    }
    if(u == y && vis[x])
    {
        printf("%d\n", Find(x));
        return;
    }
}

void init()
{
    pos = 0;
    for(int i = 1; i <= n; i++)
        pre[i] = i;
    memset(head, -1, sizeof(head));
    memset(vis, false, sizeof(vis));
    memset(in, 0, sizeof(in));
}

int main()
{
  //  freopen("in.txt", "r", stdin);
    int t, u, v;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        init();
        for(int i = 1; i < n; i++)
        {
            scanf("%d%d", &u, &v);
            add(u, v);
            in[v]++;
        }
        scanf("%d%d", &x, &y);
        for(int i = 1; i <= n; i++)
        {
            if(in[i] == 0) LCA(i);
        }
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值