最近公共祖先lca

LCA-最近公共祖先

两个点在树上距离最近的公共祖先节点

lca有主要的两种算法

1.tarjan:离线算法,复杂度O(n+q)

2.倍增|RMQ:在线算法

1.在线算法:倍增法

倍增O(nlogn)查询

怎么求lca?

1.先将深度大的移动到小的一样深

2.然后同时尽往上跳,但没就是没有跳过lca点,最后会调到lca的两个子节点上

代码:

1.建树并预处理每个点的深度,每个点的第2^i个祖先的

2.求lca,从大到小枚举,让深度大的点一直往上跳,但一种>=(深度小的)

   判断是否移动到同一点,如果同一点,直接返回

3.两个点在同一深度,这时就方便一起往上跳,但是就是不能跳到同一点,最后会跳到lca的两个子节点上

建树&&预处理:

void dfs(int x,int fa)//件树,预处理每个节点的深度与第2^i个祖先
{
    grd[x][0]=fa;
    down[x]=down[fa]+1;
    for(int i=1;i<=20;i++)
        grd[x][i]=grd[grd[x][i-1]][i-1];//预处理x的第2^i个祖先
    for(int i=head[x];i;i=next1[i])
    {
        int v=to[i];
        if(v!=fa)
            dfs(v,x);
    }
}

lca部分代码:

因为预处理出的是第2^i个祖先,所以满足单调性,可以从大到小直接枚举

int lca(int x,int y)
{
    if(down[x]<down[y])
        swap(x,y);
    for(int i=20;i>=0;i--)//x一直往上跳,但x的深度一直>=y的深度
    {
        if(down[x]-(1<<i)>=y)
            x=grd[x][i];
    }
    if(x==y)//y是x的祖先
        return x;
    for(int i=20;i>=0;i--)//一起往上跳,但是就是不能跳到同一点,最后会跳到lca的两个子节点上
    {                     //i很大当然是同一祖先,直接从大到小枚举
        if(grd[x][i]!=grd[y][i])
            x=grd[x][i],y=grd[y][i];
    }
    return grd[x][0];
}

模板题:https://www.luogu.org/problem/P3379

洛谷p3389,求lca点

#include<bits/stdc++.h>
#define MAXN 1000005
using namespace std;
int to[MAXN<<1],next1[MAXN<<1],head[MAXN<<1];
int tot=0;
int down[MAXN];
int grd[MAXN][33];

void add(int u,int v)
{
    to[++tot]=v;
    next1[tot]=head[u];
    head[u]=tot;
}

void dfs(int x,int fa)//件树,预处理每个节点的深度与第2^i个祖先
{
    grd[x][0]=fa;
    down[x]=down[fa]+1;
    for(int i=1;i<=20;i++)
        grd[x][i]=grd[grd[x][i-1]][i-1];//预处理x的第2^i个祖先
    for(int i=head[x];i;i=next1[i])
    {
        int v=to[i];
        if(v!=fa)
            dfs(v,x);
    }
}

int lca(int x,int y)
{
    if(down[x]<down[y])
        swap(x,y);
    for(int i=20;i>=0;i--)//x一直往上跳,但x的深度一直>=y的深度
    {
        if(down[x]-(1<<i)>=y)
            x=grd[x][i];
    }
    if(x==y)//y是x的祖先
        return x;
    for(int i=20;i>=0;i--)//i很大当然是同一祖先,直接从大到小枚举
    {
        if(grd[x][i]!=grd[y][i])
            x=grd[x][i],y=grd[y][i];
    }
    return grd[x][0];
}

int main()
{
    int n,m,root,a,b;
    scanf("%d%d%d",&n,&m,&root);
    for(int i=1;i<=n-1;i++)
    {
        scanf("%d%d",&a,&b);
        add(a,b);
        add(b,a);
    }
    dfs(root,0);
    while(m--)
    {
        scanf("%d%d",&a,&b);
        printf("%d\n",lca(a,b));
    }
    return 0;
}

感谢:https://blog.youkuaiyun.com/qq_42790311/article/details/81486742

求距离:

lca_tarjan算法:

#include<bits/stdc++.h>
#define pb push_back
#define MAXN 100005
#define ll long long
using namespace std;

int to[MAXN*2],next1[MAXN*2],val[2*MAXN],head[MAXN*2];

int f[MAXN];
int dis[MAXN];
int vis[MAXN];
int lca[MAXN];
int ans[MAXN];

struct node
{
    int v,id;
};

vector<node> que[MAXN];

int tot,n;

void init()
{
    tot=0;
    for(int i=1;i<=n;++i)
    {
        head[i]=-1,f[i]=i,vis[i]=0;
        que[i].clear();
    }
}

void add(int x,int y,int z)
{
    to[++tot]=y;
    val[tot]=z;
    next1[tot]=head[x];
    head[x]=tot;
}

void add_que(int a,int b,int id)
{
    que[a].pb({b,id});
}

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

void tarjan(int x)
{
    vis[x]=1;
    for(int i=head[x];i!=-1;i=next1[i])
    {
        int v=to[i];
        if(vis[v])
            continue;
        dis[v]=dis[x]+val[i];
        tarjan(v);
        f[v]=x;
    }
    int sz=que[x].size();
    for(int i=0;i<sz;i++)
    {
        int v=que[x][i].v,id=que[x][i].id;
        if(vis[v]==2)
        {
            int lcav=get(v);
            ans[id]=min(ans[id],dis[x]+dis[v]-2*dis[lcav]);
        }
    }
    vis[x]=2;
}

int main()
{
    int t,m,a,b,c;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        init();
        for(int i=1;i<n;++i)
        {
            scanf("%d%d%d",&a,&b,&c);
            if(a>b)
                swap(a,b);
            add(a,b,c);
        }
        for(int i=1;i<=m;++i)
        {
            scanf("%d%d",&a,&b);
            if(a>b)
                swap(a,b);
            if(a==b)
            {
                ans[i]=0;
            }
            else
            {
                add_que(a,b,i);
                ans[i]=1<<30;
            }
        }
        tarjan(1);
        for(int i=1;i<=m;++i)
            printf("%d\n",ans[i]);
    }
}

lca_rmq:

#include <bits/stdc++.h>

using namespace std;
const int MAXN=200005;
int n,m,tot,time1;
int to[MAXN],val[MAXN],next1[MAXN],head[MAXN];
int deep[MAXN];
int in[MAXN],out[MAXN];
int ST[MAXN*2][20];

void init()
{
    time1=tot=0;
    memset(head,-1,sizeof(head));
}

void add(int u,int v,int c)
{
    to[++tot]=v;
    val[tot]=c;
    next1[tot]=head[u];
    head[u]=tot;
}

void dfs(int x,int pre)
{
    in[x]=++time1;
    ST[time1][0]=x;
    for (int i=head[x];i!=-1;i=next1[i])
    {
        if (to[i]!=pre)
        {
            deep[to[i]]=deep[x]+val[i];
            dfs(to[i],x);
            ST[++time1][0]=x;
        }
    }
    out[x]=time1;
}

void Get_ST(int n)
{
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<20;j++)
        {
            ST[i][j]=ST[i][j-1];
            int v=i-(1<<(j-1));
            if (v>0&&deep[ST[v][j-1]]<deep[ST[i][j]])
                ST[i][j]=ST[v][j-1];
        }
    }
}

int RMQ(int L,int R)
{
    int val=floor(log(R-L+1)/log(2));
    int x=ST[L+(1<<val)-1][val],y=ST[R][val];
    return deep[x]<deep[y]?x:y;
}

int main()
{
    int t,a,b,c;
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d%d",&n,&m);
        for (int i=1;i<n;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
            add(b,a,c);
        }
        dfs(1,0);
        deep[0]=1e9;
        Get_ST(time1);
        while (m--)
        {
            scanf("%d%d",&a,&b);
            if (in[a]>in[b])
                swap(a,b);
            int LCA=RMQ(in[a],in[b]);
            printf("%d\n",deep[a]+deep[b]-deep[LCA]*2);
        }
    }
    return 0;
}

树上第k小

https://vjudge.net/problem/SPOJ-COT

题意:

求树上两点间路径第k小

解析:

数组上建主席树是root[i]在root[i-1]的基础上建链

树上建主席树是root[i]在root[fa[i]]的基础上建立链

树上两点间的路径有且仅有一条,且必过lca点,我们需要求lca

如果是求(a,b)的距离,呢么d(x,y)=d(1,x)+d(1,y)-2*d(1,lca);

但是这里不行,不能减去两个lca,x和y都算过包含1次lca,最后也要包含一次lca,所以最后减去的是fa(lca).要保留一个lca点

ac:

#include<bits/stdc++.h>
#define MAX 100005
#define MAXN 3000005
using namespace std;

int a[MAXN],b[MAXN],root[MAXN];
int L[MAXN],R[MAXN];
int sum[MAXN];
int tot=0,num=0,cnt=0;
int to[MAX<<2],nxt[MAX<<2],head[MAX<<2];
int down[MAX<<2];
int grd[100005][23];

void add(int u,int v)
{
    to[++cnt]=v;
    nxt[cnt]=head[u];
    head[u]=cnt;
}

int build(int l,int r)
{
    int now=+tot;
    sum[now]=0;
    if(l!=r)
    {
        int mid=(l+r)>>1;
        L[now]=build(l,mid);
        R[now]=build(mid+1,r);
    }
    return now;
}

int update(int pre,int l,int r,int k)
{
    int now=++tot;
    sum[now]=sum[pre]+1;
    L[now]=L[pre],R[now]=R[pre];
    if(l!=r)
    {
        int mid=(l+r)>>1;
        if(k<=mid)
            L[now]=update(L[pre],l,mid,k);
        else
            R[now]=update(R[pre],mid+1,r,k);
    }
    return now;
}

int query(int pre,int now,int lca,int flca,int l,int r,int k)
{
    if(l==r) return l;
    int mid=(l+r)>>1;
    int lcnt=sum[L[now]]+sum[L[pre]]-sum[L[lca]]-sum[L[flca]];
    if(k<=lcnt)
        return query(L[pre],L[now],L[lca],L[flca],l,mid,k);
    else
        return query(R[pre],R[now],R[lca],R[flca],mid+1,r,k-lcnt);
}

void dfs(int x,int fa)
{
    down[x]=down[fa]+1;
    grd[x][0]=fa;
    for(int i=1;i<=20;i++)//预处理倍增祖先
        grd[x][i]=grd[grd[x][i-1]][i-1];
    root[x]=update(root[fa],1,num,a[x]);//边dfs,边建链
    for(int i=head[x];i;i=nxt[i])
    {
        int v=to[i];
        if(down[v]==0)
            dfs(v,x);
    }
}

int LCA(int x,int y)//求lca
{
    if(down[x]<down[y])
        swap(x,y);
    for(int i=20;i>=0;i--)
        if(down[x]-(1<<i)>=down[y])
            x=grd[x][i];
    if(x==y)
        return y;
    for(int i=20;i>=0;i--)
        if(grd[x][i]!=grd[y][i])
            x=grd[x][i],y=grd[y][i];
    return grd[x][0];
}

void init()
{
    cnt=tot=num=0;
    memset(down,0,sizeof(down));
    memset(head,0,sizeof(head));
    memset(grd,0,sizeof(grd));
}

int main()
{
    int n,m,u,v,k;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        init();
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]),b[i]=a[i];
        sort(b+1,b+n+1);
        num=unique(b+1,b+n+1)-b-1;
        for(int i=1;i<=n;i++)
            a[i]=lower_bound(b+1,b+1+num,a[i])-b;
        for(int i=1;i<=n-1;i++)
            scanf("%d%d",&u,&v),add(u,v),add(v,u);
        root[0]=build(1,num);
        down[0]=0;
        dfs(1,0);
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&u,&v,&k);
            int lca=LCA(u,v);
            printf("%d\n",b[query(root[u],root[v],root[lca],root[grd[lca][0]],1,num,k)]);//ans=u+v-lca-flca,(保留一个lca点)
        }
    }
    return 0;
}

 

 

### 关于最近公共祖先LCA)问题的解法 #### 定义与背景 最近公共祖先(Lowest Common Ancestor, LCA),是指在一棵树中找到两个节点的最低共同父节点。这个问题在处理树形结构的数据时非常常见,在蓝桥杯竞赛以及其他编程比赛中也经常作为考察点之一。 #### 基础方法:暴力遍历 最简单的方法是从根节点开始向下逐层比较给定的两个目标节点的位置关系,直到遇到第一个能同时到达这两个节点的分支点为止。这种方法虽然直观易懂,但在大型或深层级数较多的情况下效率较低[^1]。 #### 改进方案:倍增算法 一种更高效的解决方案是采用倍增算法来求解LCA问题。此方法预先通过动态规划的方式记录下每个节点向上跳转\(2^i\)步后的祖先位置,从而可以在O(logN)时间内完成查询操作。具体步骤如下: - **预处理阶段**:对于每一个节点u及其高度h(u),计算并存储其所有可能的\(2^k\)-th父母节点parent[u][k]。 ```cpp void dfs(int u,int fa){ parent[u][0]=fa; depth[u]=depth[fa]+1; for (int i=1;(1<<i)<=depth[u];++i) parent[u][i]=parent[parent[u][i-1]][i-1]; // ...其他逻辑... } ``` - **查询阶段**:当需要寻找两节点u和v之间的LCA时,先调整两者至相同深度再逐步上移直至相遇。 ```cpp int lca_query(int u,int v){ if(depth[u]<depth[v]) swap(u,v); while(depth[u]>depth[v]){ int k=log2(depth[u]-depth[v]); u=parent[u][k]; } if(u==v)return u; for(int k=max_level;k>=0;--k){ if(parent[u][k]!=parent[v][k]){ u=parent[u][k]; v=parent[v][k]; } } return parent[u][0]; } ``` 这种基于倍增的思想不仅适用于普通的无权有向树,也可以扩展到加权边的情况,并且能够很好地满足比赛中的时间复杂度要求[^2]。 #### 应用于蓝桥杯竞赛 考虑到蓝桥杯对参赛者的基础知识掌握程度有一定要求,建议深入理解上述两种基本策略的基础上,多做练习题巩固知识点。特别是针对不同类型的输入规模优化自己的解答方式,提高程序运行速度和准确性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值