树的LCA

2 篇文章 0 订阅

LCA

概念

最近公共祖先

模板

题目描述
给定n个点的树(1是根),m次询问,每次询问两点的LCA;

输入格式
第一行两个整数,n,m;

接下来n-1行,给定正整数a,b,表示a,b间有边;

接下来m行,给定整数a,b,表示询问a,b;

输出格式
对于每个询问,输出它们的LCA;

样例数据
input

3 2
1 2
2 3
1 2
2 3
output

1
2
数据规模与约定
保证所有数据n,m<=100000

方法1(tarjan“向上标记法”)

dfs一条路搜到底时,搜到了起点,在回溯返回时每次将子节点通过并查集合并到父节点上,当搜到终点时,那么father[起点]就是起点和终点的转折点LCA。
这里写图片描述

code1

#include<bits/stdc++.h>
using namespace std;
int n,m,t1,t2,s;
struct edge
{
    int y,next,num;
}a1[1000010];
edge a2[1000010];
bool vis[500010]={};
int father[500010]={},ans[500010]={},link1[500010]={},link2[500010]={};
void insert1(int x,int y){a1[++t1].y=y;a1[t1].next=link1[x];link1[x]=t1;}
void insert2(int x,int y,int num){a2[++t2].y=y;a2[t2].next=link2[x];link2[x]=t2;a2[t2].num=num;}
void init()
{
    scanf("%d%d%d",&n,&m,&s);
    int x,y;
    for(int i=1;i<n;++i)
      {
        scanf("%d%d",&x,&y);
        insert1(x,y);
        insert1(y,x);
        father[i]=i;
      }
    father[n]=n;
    for(int i=1;i<=m;++i)
      {
        scanf("%d%d",&x,&y);
        insert2(x,y,i);
        insert2(y,x,i);
      }
}
int getfa(int x)
{
    return x==father[x] ? x:father[x]=getfa(father[x]);
}
void dfs(int x,int fa)
{
    vis[x]=1;
    for(int i=link2[x];i;i=a2[i].next)
      if (vis[a2[i].y])
        ans[a2[i].num]=getfa(a2[i].y);
    for(int i=link1[x];i;i=a1[i].next) 
      if (a1[i].y!=fa)
        dfs(a1[i].y,x);
    father[x]=fa;
}
int main()
{
    init();
    dfs(s,0);
    for(int i=1;i<=m;++i)
      printf("%d\n",ans[i]);
    return 0;
}

方法2(RMQ树上倍增法)

需要一个数组F[i][j],表示i节点之前第2^j号祖先。当然,F[i][0]即是i的父节点。
F[i][j]==F[F[i][j-1]][j-1].
那么可以O(nlogn)预处理出数组F

void prepare()
{
    int fence=log(1.0*(max_dep))/log(2.0);
    p[1][0]=-1;
    for(int j=1;j<=fence;j++)
        for(int i=1;i<=n;i++)
            if(p[i][j-1]!=-1)
                p[i][j]=p[p[i][j-1]][j-1];
}

对于每个起点a,终点b。
先求出他们的深度数组与F数组
每次求LCA将两个点深度调为一致。然后可以通过一起向上跳的方法求出LCA。

int LCA(int a,int b)
{
    if(a==b) return a;
    if(dep[b]>dep[a]) swap(a,b);
    int D;
    for(D=0;(1<<D)<=dep[a];D++);
    D--; //确定a的最大2次幂深度;
    for(int j=D;j>=0;j--)
        if(dep[a]-(1<<j)>=dep[b])
            a=p[a][j]; //令a与b深度相同;
    if(a==b) return a;
    for(int j=D;j>=0;j--)
        if(p[a][j]!=-1 && p[a][j]!=p[b][j])
            a=p[a][j],b=p[b][j];
    return p[a][0];
}

code

#include<bits/stdc++.h>
using namespace std;
int n,m,x,y,t=0;
int p[100010][50]={},dep[100010]={};
struct note
{
    int y,next;
}e[200010]={};
int linkk[100010]={};
void insert(int x,int y)
{
    e[++t].y=y;
    e[t].next=linkk[x];
    linkk[x]=t;
}
void dfs(int x,int father)
{
    p[x][0]=father;
    for(int i=linkk[x];i;i=e[i].next)
      if (e[i].y!=father)
        dep[e[i].y]=dep[x]+1,dfs(e[i].y,x);
}
void make_()
{
    int xx=log(1.0*n)/log(2.0);
    for(int j=1;j<=xx;++j)
      for(int i=1;i<=n;++i)
        if (p[i][j-1]!=-1)
          p[i][j]=p[p[i][j-1]][j-1];
}
int LCA(int xx,int yy)
{
    if (xx==yy) return xx;
    if (dep[xx]<dep[yy]) swap(xx,yy);
    int D;
    for(D=0;(1<<D)<=dep[xx];++D);
    --D;
    for(int i=D;i>=0;--i)
      if (dep[xx]-(1<<i)>=dep[yy])
        xx=p[xx][i];
    if (xx==yy) return xx;
    for(int i=D;i>=0;--i)
      if (p[xx][i]!=-1&&p[xx][i]!=p[yy][i])
        xx=p[xx][i],yy=p[yy][i];
    return p[xx][0];
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;++i)
      {
        scanf("%d%d",&x,&y);
        insert(x,y);
        insert(y,x);
      }
    dfs(1,-1);
    make_();
    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、付费专栏及课程。

余额充值