树上倍增法求最近公共祖先模板

1.学习博客

2.求最近公共祖先的用处(题型)

1.求两点之间的树上距离:x的深度+y的深度-2*LCA(x,y)
2.倍增法求最近公共祖先是树上差分实现的基础,目的是为了求树上某一条边或者某一个点被被覆盖过的次数。
3.解决次小生成树问题。

3.模板

#include <bits/stdc++.h>
using namespace std;
int n,m,s,x,y,tot=0;
const int N=100005;//N存储节点总数,M存储边的总数

int deep[N],fa[N][22],lg[N];
//deep[i]是i号节点的深度
//lg是log数组
struct node
{
    int u,v,next;
}edge[4*N];
int cnt;
int head[N];
void add(int u,int v)//链式前项星加边
{
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].next=head[u];
    head[u]=cnt;
    cnt++;
}
void dfs(int x,int y)
{
    deep[x]=deep[y]+1;//x是y的儿子节点,所以要+1
    fa[x][0]=y;//fa[x][0]表示x的父亲节点,而y是x的父亲节点.
    for(int i=1; (1<<i)<=deep[x]; i++) //2^i<=deep[x]表示不能跳出去了,最多跳到根节点上面
        fa[x][i]=fa[fa[x][i-1]][i-1];//状态转移 2^i=2^(i-1)+2^(i-1)
    for(int i=head[x];~i;i=edge[i].next) //遍历所有的出边
        if(edge[i].v!=y)//因为是无向图,所以要避免回到父亲节点上面去了
            dfs(edge[i].v,x);//访问儿子节点,并且标记自己是父亲节点
    return ;//返回
}
int LCA(int x,int y)
{
    if(deep[x]<deep[y])//强制要求x节点是在下方的节点
        swap(x,y);//交换,维持性质
    while(deep[x]>deep[y])//当我们还没有使得节点同样深度
        x=fa[x][lg[deep[x]-deep[y]]];//往上面跳跃,deep[x]-deep[y]是高度差.
    if(x==y)//发现Lca(x,y)=y
        return x;//返回吧,找到了...
    for(int k=lg[deep[x]]; k>=0; k--) //从大到小,枚举我们所需要的长度.2^(log(deep[x]))~1
        if(fa[x][k]!=fa[y][k])//如果发现x,y节点还没有上升到最近公共祖先节点
        {
            x=fa[x][k];//当然要跳跃
            y=fa[y][k];//当然要跳跃
        }
    return fa[x][0];//必须返回x的父亲节点,也就是Lca(x,y)
}
void init()
{
    lg[0]=-1;
    for(int i=1;i<=N;i++)
        lg[i]=lg[i>>1]+1;
    for(int i=1;i<=N;i++)
         head[i]=-1;

}
int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    freopen("E:\\in.txt","r",stdin);
    init();
    scanf("%d%d%d",&n,&m,&s);//n个节点,m次询问,s为根节点
    for(int i=1; i<n; i++) //n-1条边
    {
        scanf("%d%d",&x,&y);//读入边
        add(x,y);//建立边
        add(y,x);//建立无向图
    }
    dfs(s,0);//从根节点,开始建立节点之间的跳跃关系,根节点的父亲节点没有,故选择0
    for(int i=1; i<=m; i++)
    {
        scanf("%d%d",&x,&y);//读入需要查询的节点
        printf("%d\n",LCA(x,y));//输出查询的结果
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值