【树剖求LCA】树剖知识点

不太优美但是有注释的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include<cstdio>
#include<iostream>
using namespace std;
struct edge{
    int to,ne;
}e[1000005];
int n,m,s,ecnt,head[500005],dep[500005],siz[500005],son[500005],top[500005],f[500005];
void add(int x,int y)    //加边
{
    e[++ecnt].to=y;
    e[ecnt].ne=head[x];
    head[x]=ecnt;
}
void dfs1(int x)                             //构造树
{
    siz[x]=1;                                //假设当前节点仅有一个儿子
    dep[x]=dep[f[x]]+1;                      //当前节点深度=父亲节点深度+1
    for(int i=head[x];i;i=e[i].ne)          //遍历所有的子节点
    {
        int dd=e[i].to;
        if(dd==f[x])continue;               //如果是父节点,则略过
        f[dd]=x;                             //那么确定x是当前节点的父亲
        dfs1(dd);                            //向下遍历
        siz[x]+=siz[dd];                     //遍历完子树之后,加上子树的大小
        if(!son[x]||siz[son[x]]<siz[dd])    //如果x节点重儿子未确定或者重儿子的子树比当前遍历节点的子树小
            son[x]=dd;                       //更新重儿子
    }
}
 
void dfs2(int x,int tv)                     //求重链
{
    top[x]=tv;                               //设置x所在重链顶为tv
    if(son[x])dfs2(son[x],tv);              //如果x有重儿子,那么随着这条重链走
    for(int i=head[x];i;i=e[i].ne)           
    {
        int dd=e[i].to;
        if(dd==f[x]||dd==son[x])continue//如果走到父亲或者走到重儿子(已经走过重儿子,避免重复),那么跳过
        dfs2(dd,dd);                        //开启一条新链,链顶是其本身
    }
}
int lca(int x,int y)
{
    while(top[x]!=top[y])                              //如果二者不在同一条重链上
    {
        if(dep[top[x]] >= dep[top[y]]) x=f[top[x]];   //选择所在重链的顶的深度较大的点向上跳,目的是防止跳过LCA
        else y=f[top[y]];
    }
    return dep[x] < dep[y] ?x :y;                     //当二者在同一条重链上的时候,选择深度较浅的点即为lca
     
     
}
int main()
{
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<n;++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    dfs1(s);
    dfs2(s,s);
    for(int i=1;i<=m;++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
}

  

 

比较优美但是没注释的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#include<iostream>
#include<vector>
#include<cstdio>
#include<queue>
#include<map>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<set>
#include<cstring>
using namespace std;
typedef long long ll;
const ll INF=99999999;
const int N = 500010;
 
int n,m,s;
 
struct edge{
    int to,ne;
     
}e[N*2];
 
int top[N],siz[N],son[N],fa[N],dep[N];
int head[N],ecnt = 1;
 
void add(int x,int y)
{
    e[ecnt].to = y;
    e[ecnt].ne = head[x];
     
    head[x] = ecnt++;
}
 
void dfs1(int x)
{
    siz[x] = 1;
    dep[x] = dep[fa[x]] + 1;
     
    for(int i = head[x];i;i = e[i].ne){
        int t = e[i].to;
        if(t == fa[x]) continue;
        fa[t] = x;
         
        dfs1(t);
        siz[x] += siz[t];
        if(!son[x]||siz[son[x]] < siz[t])
            son[x] = t;
    }
}
 
void dfs2(int x,int tp)
{
    top[x] = tp;
     
    if(son[x])
        dfs2(son[x],tp);
         
    for(int i = head[x];i;i = e[i].ne){
        int t = e[i].to;
        if(t == son[x]||t == fa[x]) continue;
         
        dfs2(t,t);
    }  
 
}
 
int lca(int x,int y)
{
    while(top[x] != top[y]){
        if(dep[top[x]] >= dep[top[y]]) x = fa[top[x]];
        else y = fa[top[y]];
    }
    return dep[x] < dep[y] ?x :y;
}
int main()
{
     
    scanf("%d%d%d",&n,&m,&s);
    for(int i = 1;i < n;i++){
        int x,y;
        scanf("%d%d",&x,&y);
         
        add(x,y);
        add(y,x);
    }
    dfs1(s);
    dfs2(s,s);
     
    for(int i = 1;i <= m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
         
        printf("%d\n",lca(a,b));
    }
    return 0;
}

 

树剖理解容易,需要注意的是题目如果给的是双向边,e数组需要开两倍于边数

 

  

posted @   dudujerry  阅读(358)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示