LCA最近公共祖先

                                                                               LCA最近公共祖先 

 

该分析转之:http://kmplayer.iteye.com/blog/604518

 

1,并查集+dfs
对整个树进行深度优先遍历,并在遍历的过程中不断地把一些目前可能查询到的并且结果相同的节点用并查集合并.

2,分类,使每个结点都落到某个类中,到时候只要执行集合查询,就可以知道结点的LCA了。
对于一个结点u.类别有:
以u为根的子树、除类一以外的以f(u)为根的子树、除前两类以外的以f(f(u))为根的子树、除前三类以外的以f(f(f(u)))为根的子树……

类一的LCA为u,类二为f(u),类三为f(f(u)),类四为f(f(f(u)))。这样的分类看起来好像并不困难。
但关键是查询是二维的,并没有一个确定的u。接下来就是这个算法的巧妙之处了。
利用递归的LCA过程。
当lca(u)执行完毕后,以u为根的子树已经全部并为了一个集合。而一个lca的内部实际上做了的事就是对其子结点,依此调用lca.
当v1(第一个子结点)被lca,正在处理v2的时候,以v1为根的子树+u同在一个集合里,f(u)+编号比u小的u的兄弟的子树 同在一个集合里,f(f(u)) + 编号比f(u)小的 f(u)的兄弟 的子树 同在一个集合里…… 
而这些集合,对于v2的LCA都是不同的。因此只要查询x在哪一个集合里,就能知道LCA(v2,x)

还有一种可能,x不在任何集合里。当他是v2的儿子,v3,v4等子树或编号比u大的u的兄弟的子树(等等)时,就会发生这种情况。即还没有被处理。还没有处理过的怎么办?把一个查询(x1,x2)往查询列表里添加两次,一次添加到x1的列表里,一次添加到x2的列表里,如果在做x1的时候发现 x2已经被处理了,那就接受这个询问。(两次中必定只有一次询问被接受).

#include <iostream>
#include <algorithm>
#include <vector>
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 100000 + 10;
int degree[MAXN];
bool vst[MAXN];
int ancestor[MAXN];
int f[MAXN];
int rank[MAXN];
vector<int> tree[MAXN];
vector<int> Qes[MAXN];

int N;
void init(){
    for(int i = 0;i <= N;++i){
         degree[i] = 0;
         vst[i] = false;
         ancestor[i] = -1;
         f[i] = i;
         rank[i] = 0;
         tree[i].clear();
         Qes[i].clear();
    }
}

int find(int x){
    if(x == f[x])
        return x;
    return f[x] = find(f[x]);
}

void setUnion(int u,int v){
    int a = find(u),b = find(v);
    if(a != b){
        if(rank[a] < rank[b]){
            f[a] = b;
        } else {
            f[b] = a;
            if(rank[a] == rank[b]) rank[a]++;
        }
    }
}

void LCA(int u){
    ancestor[u] = u;
    int sz = tree[u].size();
    for(int i = 0;i < sz;++i){
        LCA(tree[u][i]);
        setUnion(u,tree[u][i]);
        ancestor[find(u)] = u;
    }

    vst[u] = 1;
    sz = Qes[u].size();
    for(int i = 0;i < sz;++i){
        if(vst[Qes[u][i]]){
            printf("%d\n",ancestor[find(Qes[u][i])]);
            return;
        }
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
        int x,y;
        scanf("%d",&N);
        init();
        for(int i = 1;i < N;++i){
            scanf("%d%d",&x,&y);
            degree[y]++;
            tree[x].push_back(y);
        }

        int s,t;
        scanf("%d%d",&s,&t);
        Qes[s].push_back(t);
        Qes[t].push_back(s);

        for(int i = 1;i <= N;++i){
            if(degree[i] == 0){
                LCA(i);
                break;
            }
        }
    }
    return 0;
}


 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值