树上倍增求LCA

简介: 题目描述如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。输入格式第一行包含三个正整数 N,M,SN,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。接下来 N-1N−1 行每行包含两个正整数 x, yx,y,表示 xx 结点和 yy 结点之间有一条直接连接的边(数据保证可以构成树)。接下来 MM 行每行包含两个正整数 a, ba,b,表示询问 aa 结点和 bb 结点的最近公共祖先。

题目链接


题目描述


如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。


输入格式


第一行包含三个正整数 N,M,SN,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来 N-1N−1 行每行包含两个正整数 x, yx,y,表示 xx 结点和 yy 结点之间有一条直接连接的边(数据保证可以构成树)。

接下来 MM 行每行包含两个正整数 a, ba,b,表示询问 aa 结点和 bb 结点的 最近公共祖先


输出格式


输出包含 MM 行,每行包含一个正整数,依次为每一个询问的结果。


输入输出


样例 1

输入

5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5


输出


4
4
1
4
4


说明/提示

对于 30% 的数据,N≤10,M≤10。

对于 70% 的数据,N≤10000,M≤10000。

对于 100% 的数据,N≤500000,M≤500000。

微信图片_20220608211303.jpg

PICTURE:/home/ldu/.config/tencent-qq//AppData/file//sendpix2.jpg

在进行倍增的时候,考虑跳的步长从大到小进行安排,并且求一下log来优化

一些小细节:

对于该题可以采取链式前向星建图(vector也无所谓),再加边的时候应该注意是双向边,而不是单向的边


代码详解:


一:初始化头结点为-1

void init(){
    for(int i=1;i<=n;i++) head[i] = -1;
}


二:对输入数据进行加边操作:

void add(int u,int v){
    e[cnt].u = u;
    e[cnt].v = v;
    e[cnt].next = head[u];
    head[u] = cnt ++;
}


三:对于里面的DFS操作,就是对某一个节点的2^j级祖先进行预处理,采用DFS的方式来进行递归,表示在数组里面就是fa[x][j] ,在求解的过程中还要递归处理节点的深度,对给出的树根,可以巧妙的处理为这颗树根的祖先节点是0这个点,然后他的深度是1,在递归的过程中cur表示当前节点,而rt表示这个节点的根节点(也就是祖先节点),然后就可以在递归的过程中,将这个点的深度depth[cur] = depth[fa] + 1。


然后下面的for循环处理fa数组,这个循环所能走的最大2^j级祖先最大的j可以对他预处理一个log,也就是下面的 cal 函数。处理完之后就可以遍历和这个节点相连的所有节点,当这个点不是这个节点的根节点也就是rt的时候,对他进行递归的DFS处理,此时在传递参数的时候父亲节点就是cur,当前节点就是与其相连的节点


void dfs(int cur,int rt){
    fa[cur][0] = rt,dep[cur] = dep[rt] + 1;/// the depth of the current node changed
    for(int i=1;i <= lg[dep[cur]];i++){
        fa[cur][i] = fa[fa[cur][i-1]][i-1];
    }
    for(int i=head[cur];~i;i = e[i].next){/// visit all linked nodes
        if(e[i].v != rt) dfs(e[i].v,cur);
    }
}


四:lca函数


我们在处理的过程中,默认x节点的深度比y深,所以说如果说x深度比y深度小,就直接交换两个节点,方便下次进行处理


然后一直遍历,当x深度一直大于y深度的时候,我们对x一直进行转移


然后我们在下面从大到小转移节点,同时对两个节点进行转移处理,从大到小的过程中我们对他处理的过程是:根据预先处理的log大小到0,来进行从大到小的跳转,在跳转的过程中,我们要避免讲两个点跳转到同一个节点,如果能跳转到的节点不相同我们就跳转,相同的情况下不进行跳转;


关于为什么从大到小:从大到小的过程中一定有一种方式能够将这个数唯一的表示出来,而且不存在所谓的“反悔”的情况


在最后返回的时候,返回fa[x][0]即为答案

int lca(int x,int y){
    if(dep[x] < dep[y]) swap(x,y);
    while(dep[x] > dep[y]) x = fa[x][lg[dep[x] - dep[y]] - 1];
    if(x == y) return y;
    /// big -> small
    for(int k = lg[dep[x]] - 1;k >= 0;k --){
        if(fa[x][k] != fa[y][k]){
            x = fa[x][k];
            y = fa[y][k];
        }
    }
    return fa[x][0];
}


有待完善

欢迎批评指正

版本一时间:2021-03-07-22:11


int n,m;
int root;
int head[maxn];
struct node{
    int u;
    int v;
    int next;
}e[maxn];
int dep[maxn];/// save the depth of every node
int fa[maxn][30];
int lg[maxn];
int cnt = 0;
void init(){
    for(int i=1;i<=n;i++) head[i] = -1;
}
void add(int u,int v){
    e[cnt].u = u;
    e[cnt].v = v;
    e[cnt].next = head[u];
    head[u] = cnt ++;
}
void dfs(int cur,int rt){
    fa[cur][0] = rt,dep[cur] = dep[rt] + 1;/// the depth of the current node changed
    for(int i=1;i <= lg[dep[cur]];i++){
        fa[cur][i] = fa[fa[cur][i-1]][i-1];
    }
    for(int i=head[cur];~i;i = e[i].next){/// visit all linked nodes
        if(e[i].v != rt) dfs(e[i].v,cur);
    }
}
void cal(){
    for(int i=1;i<=n;i++){
        lg[i] = lg[i-1] + (1 << lg[i-1] == i);/// 2 ^ lg[i-1] == i true + 1
    }
}
int lca(int x,int y){
    if(dep[x] < dep[y]) swap(x,y);
    while(dep[x] > dep[y]) x = fa[x][lg[dep[x] - dep[y]] - 1];
    if(x == y) return y;
    /// big -> small
    for(int k = lg[dep[x]] - 1;k >= 0;k --){
        if(fa[x][k] != fa[y][k]){
            x = fa[x][k];
            y = fa[y][k];
        }
    }
    return fa[x][0];
}
int main() {
    ///freopen("1.in","r",stdin);
    ///freopen("1.out","w",stdout);
    cin >> n >> m >> root;
    cal();
    init();
    for(int i=1;i<n;i++){
        int x = read,y = read;
        add(x,y);
        add(y,x);
    }
    dfs(root,0);
    for(int i=1;i<=m;i++){
        int x = read,y = read;
        printf("%d\n",lca(x,y));
    }
  return 0;
}/// ac_code


目录
相关文章
|
6月前
|
算法
Tarjan 求有向图的强连通分量
Tarjan算法是一种用于查找有向图中强连通分量的高效方法。作者分享了自己的笔记,强调了网上解释的不清晰性,并引用了OI Wiki的资源。算法维护每个节点的`dfn`(深度优先搜索顺序)和`low`(能到达的最小DFS序)。通过遍历节点和其邻接节点,更新`low`值,识别环路。当`dfn[x] == low[x]`,表示找到一个强连通分量。代码示例展示了具体的实现细节。
|
7月前
Luogu P3379 【模板】最近公共祖先(LCA),树链剖分求LCA模板
Luogu P3379 【模板】最近公共祖先(LCA),树链剖分求LCA模板
39 0
|
7月前
|
算法 vr&ar Windows
Tarjan算法求LCA(最近公共祖先)
Tarjan算法求LCA(最近公共祖先)
43 0
|
7月前
tarjan求强连通分量
tarjan求强连通分量
32 0
|
算法 C++
最近公共祖先(LCA)
最近公共祖先(LCA)
58 0
P3379 【模板】最近公共祖先(LCA)
P3379 【模板】最近公共祖先(LCA)
65 0
牛客—— 小A的最短路 (LCA)
牛客—— 小A的最短路 (LCA)
92 0
|
算法
图论最短路及生成树(Prim,Djikstra,Spfa,Bellan-ford,kruskal,topsort)
图论最短路及生成树(Prim,Djikstra,Spfa,Bellan-ford,kruskal,topsort)
142 1
|
JavaScript
二刷--二叉树的最近公共祖先
二刷--二叉树的最近公共祖先
123 0
二刷--二叉树的最近公共祖先