LCA入门

这篇博客介绍了LCA(Lowest Common Ancestor)的基础知识,包括如何计算节点间的距离,并给出了两个具体的题目实例:HDU 2586和ZJU 3195。博主分享了使用倍增法和Tarjan算法解决LCA问题的思路,并提供了相关教程链接。对于Tarjan算法,博主遇到了WA(Wrong Answer)情况,正在寻求解决方案。
摘要由CSDN通过智能技术生成

关于LCA几个讲解:

https://blog.csdn.net/jeryjeryjery/article/details/52853017

1.How far away ?

http://acm.hdu.edu.cn/showproblem.php?pid=2586

题目大意:给出n个房屋,这些房屋之间的链接是树形链接,房屋和房屋之间有且只有一个路线;道路是双向的。

T组数据;

n个房子,m次查询;

n-1个路径  :a,b之间的距离是l

 

倍增:

倍增法讲解https://blog.csdn.net/lw277232240/article/details/72870644

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int MAX=1e6+5;
const int INF=0x3f3f3f3f;

struct node{
    int t,next;
    ll len;
    //Node(int T=0,int Next=-1,ll Len=0):t(T),next(Next),len(Len){}

}edge[MAX<<1];
int head[MAX],cnt;
void initedge(int n)
{
    memset(head,-1,sizeof(head));cnt=0;
}
void addedge(int u,int v,ll len=0)
{
    edge[cnt]=node{v,head[u],len};
//这样写不加 Node(int T=0,int Next=-1,ll Len=0):t(T),next(Next),len(Len){} 最好用G++提交
    head[u]=cnt++;
}

int T,n,m;

int deep[MAX];
int anc[202020][21];
int dis[202020];

void dfs(int u,int father)
{
    for(int i=1;i<=20;i++)
    {
        anc[u][i]=anc[ anc[u][i-1] ][i-1];//找u的2的i次方的父节点
    }
    for(int i=head[u];~i;i=edge[i].next)
    {
        int t=edge[i].t;
        if(t==father)continue;
//因为这里建立的是双向图!!!addedge(u,v,w); addedge(v,u,w);两张方向相反的图

        dis[t]=dis[u]+edge[i].len;
        deep[t]=deep[u]+1;
        anc[t][0]=u;
        dfs(t,u);
    }
}
///查询
int LCA(int u,int v)
{
    if(deep[u]<deep[v])swap(u,v); ///1.调整u深
    for(int i=20;i>=0;i--)    ///2.调整到统一深度
    {
        if(deep[anc[u][i]] >= deep[v])
            u=anc[u][i];
    }
    if(u==v)return u;

    for(int i=20;i>=0;i--) ///3.一起往上跳
    {
        if(anc[u][i] != anc[v][i])
        {
            u=anc[u][i];
            v=anc[v][i];
        }
    }
    return anc[u][0];
}
int main()
{
    for(scanf("%d",&T);T--;)
    {
        scanf("%d%d",&n,&m);
        initedge(n);
        for(int i=1;i<n;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
            addedge(v,u,w);
        }

        dis[1]=0;
        deep[1]=1;
        anc[1][0]=1;
        dfs(1,1);

        for(int i=0;i<m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            int k=LCA(u,v);

            int ans=dis[u]+dis[v]-2*dis[k];
            printf("%d\n",ans);
        }

    }
}

tarjan: 


#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <math.h>
#define ll long long
const int maxn=1e5+10;
using namespace std;
int rlen[maxn];
int vis[maxn];
int fa[maxn];
struct node
{
    int u,v,w,next;
}edge[maxn];
struct query
{
    int u,v,lca,next;
}edgeq[maxn*4];

int head[maxn],cnt;
int headq[maxn],cntq;

void addedge(int u,int v,int len)
{
    edge[cnt]=node{u,v,len,head[u]};
    head[u]=cnt++;
}

void addq(int u,int v){    //询问
    edgeq[cntq]=query{u,v,0,headq[u]};
    headq[u]=cntq++;
}

void  initedge()
{
    cnt=0;
    memset(head,-1,sizeof(head));
}
void initq()
{
    cntq=0;
    memset(headq,-1,sizeof(headq));
}


int Find(int u)
{
    if(u==fa[u])
        return u;
    return fa[u]=Find(fa[u]);
}
void Union(int x,int y)

{
    x=Find(x);
    y=Find(y);
    if(x!=y)
    fa[y]=x;
}

void dfs(int u)//注意理解递归的过程!
{
    vis[u]=1;
    fa[u]=u;
//通过递归更新子节点到根节点的距离和更新子节的祖节点的位置
    for(int i=head[u];~i;i=edge[i].next)
    {
        int t=edge[i].v;

        if(!vis[t])
        {
            rlen[t]=rlen[u]+edge[i].w;
            dfs(t);
            Union(u,t);//遍历完到最底层后,回溯时再更新父节点!!1
        }
    }
//每遍历完一个父节点的所有子节点,就与父节点相关的最近LCA的询问
    for(int i=headq[u];~i;i=edgeq[i].next)
    {
        int v=edgeq[i].v;
        if(vis[v])//重点!如果该子节点已经被访问过,那么父节点与该子节点的最近LCA一定就是子节点的祖节点
        {
            int lca=Find(v);
            edgeq[i].lca=lca;
        }
    }
}


int main()
{
    int cas,n,m,u,v,len;
    cin>>cas;
    while(cas--)
    {
        scanf("%d%d",&n,&m);
        initedge();
        initq();

        for(int i=0;i<n-1;i++){
            scanf("%d%d%d",&u,&v,&len);
            addedge(u,v,len);
            addedge(v,u,len);
        }

        ///输入询问 u v
        for(int i=0;i<m;i++)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            addq(u,v);
            addq(v,u);
        }
        memset(vis,0,sizeof(vis));
        rlen[1]=0;
        dfs(1); //LCA
        for(int i=0;i<m;i++)
        {
            //应为是双向图,所以要乘以2
            int ans=rlen[edgeq[2*i].u]+rlen[edgeq[2*i].v]-2*rlen[edgeq[2*i].lca];
            printf("%d\n",ans);
        }

    }
}

2.Design the city

http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3195

题意:

给定N个点,下面N-1行u、v、d表示一条无向边和边权值,它们构成了一棵无向树。现在给出Q查询。

问在树上连通a、b、c三点的最短距离。


显然在树上连通a、b、c三点最短距离 为dist[a] + dist[b] + dist[c] - (dist[LCA(a, b)] + dist[LCA(a, c)] + dist[LCA(b, c)])。其中dist[]存储节点到根的最短距离。

答案也就是

(lca(a,b)+lca(b,c)+lca(a,c))/2
lca(a,b)=dist[a] + dist[b] -- 2*dist[lca(a, b)]

注意输出格式!!!!
 

倍增:

#include <stdio.h>
 
#include <string.h>
 
#include <iostream>
 
#include <algorithm>
 
#include <math.h>
 
#define ll long long
 
const int MAX=1e5+10;
 
using namespace std;
 
struct node{
    int t,next;
    ll len;
}edge[MAX<<1];
int head[MAX],cnt;
void initedge()
{
    memset(head,-1,sizeof(head));
    cnt=0;
}
void addedge(int u,int v,ll len=0)
{
    edge[cnt]=node{v,head[u],len};
    head[u]=cnt++;
}
 
int T,n,m;
 
int deep[MAX];
int anc[202020][21];
int dis[202020];
 
void dfs(int u,int father)
{
    for(int i=1;i<=20;i++)
    {
        anc[u][i]=anc[ anc[u][i-1] ][i-1];//找u的2的i次方的父节点
    }
    for(int i=head[u];~i;i=edge[i].next)
    {
        int t=edge[i].t;
        if(t==father)continue;
 
        dis[t]=dis[u]+edge[i].len;
        deep[t]=deep[u]+1;
        anc[t][0]=u;
        dfs(t,u);
    }
}
///查询
int LCA(int u,int v)
{
    if(deep[u]<deep[v])swap(u,v); ///1.调整u深
    for(int i=20;i>=0;i--)    ///2.调整到统一深度
    {
        if(deep[anc[u][i]] >= deep[v])
            u=anc[u][i];
    }
    if(u==v)return u;
 
    for(int i=20;i>=0;i--) ///3.一起往上跳
    {
        if(anc[u][i] != anc[v][i])
        {
            u=anc[u][i];
            v=anc[v][i];
        }
    }
    return anc[u][0];
}
int jisuan(int u,int v)
{
      int ans=(dis[u]+dis[v]-2*dis[LCA(u,v)]);
      return ans;
}
 
int main()
{
    int flag=0;//注意输出格式!!!
    while(~scanf("%d",&n))
    {
        //注意输出格式!!!
        if(flag)
              printf("\n");
        flag=1;
        
        initedge();
        for(int i=1;i<n;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
            addedge(v,u,w);
        }
        dis[1]=0;
        deep[1]=1;
        anc[1][0]=1;
        dfs(1,1);
        scanf("%d",&m);
        for(int i=0;i<m;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            int sum=jisuan(u,v)+jisuan(u,w)+jisuan(v,w);
            printf("%d\n",sum/2);
        }
    }
}

tarjan(WA了,但不知道错在哪了啊?):

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <math.h>
#define ll long long
using namespace std;
const int nmaxn=50000+10;
const int qmaxn=70000+10;
ll rlen[qmaxn];
int vis[qmaxn];
int fa[qmaxn];
struct node
{
    int u,v;
    ll w;
    int next;
    node (int U=0,int V=0,ll W=0,int N=0):
        u(U),v(V),w(W),next(N){}
}edge[nmaxn<<1];// 注意数组开的大小!
struct query
{
    int u,v,lca,next;
 
}edgeq[qmaxn*6];// 注意数组开的大小!
 
int head[nmaxn],cnt;
int headq[qmaxn],cntq;
 
void addedge(int u,int v,ll len)
{
    edge[cnt]=node(u,v,len,head[u]);
    head[u]=cnt++;
}
 
void addq(int u,int v)
{
    edgeq[cntq]=query{u,v,-1,headq[u]};
    headq[u]=cntq++;
}
 
void  initedge()
{
    cnt=0;
    memset(head,-1,sizeof(head));
}
void initq()
{
    cntq=0;
    memset(headq,-1,sizeof(headq));
}
 
 
int Find(int u)
{
    if(u==fa[u])
        return u;
    return fa[u]=Find(fa[u]);
}
void Union(int x,int y)
 
{
    x=Find(x);
    y=Find(y);
    if(x!=y)
        fa[y]=x;
}
 
void dfs(int u)
{
    vis[u]=1;
    fa[u]=u;
    for(int i=head[u];~i;i=edge[i].next)
    {
        int t=edge[i].v;
 
        if(!vis[t])
        {
            rlen[t]=rlen[u]+edge[i].w;// 注意这里!
            dfs(t);
            Union(u,t);
        }
    }
 
    for(int i=headq[u];~i;i=edgeq[i].next)
    {
        int v=edgeq[i].v;
        if(vis[v])// 注意这里已标记过的
        {
            int lca=Find(v);
            edgeq[i].lca=lca;
        }
    }
}
 
 
ll jisuan(int x)
{
 
	return rlen[edgeq[x].u]+rlen[edgeq[x].v]-2*rlen[edgeq[x].lca];
 
}
 
int main()
{
    int flag,n,m,u,v,w;
    ll len;
    flag=0;
    while(~scanf("%d",&n))
    {
        if(flag)
            printf("\n");
        flag=1;
        initedge();
        initq();
        memset(vis,0,sizeof(vis));
        memset(fa,0,sizeof(fa));
        memset(rlen,0,sizeof(rlen));
 
        for(int i=0;i<n-1;i++)
        {
            scanf("%d%d%lld",&u,&v,&len);
            addedge(u,v,len);
            addedge(v,u,len);
        }
 
        scanf("%d",&m);
 
        for(int i=0;i<m;i++)
        {
 
            scanf("%d%d%d",&u,&v,&w);
            addq(u,v);
            //addq(v,u);
 
            addq(u,w);
            //addq(w,u);
 
            addq(v,w);
            //addq(w,v);
        }
        dfs(0); //LCA
        for(int i=0;i<m;i++)
        {
            int a=i*3,b=a+1,c=a+2;//注意这里与开的树大小的关系!
            ll ans=jisuan(a)+jisuan(b)+jisuan(c);
            printf("%lld\n",ans/2);
        }
 
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值