倍增法 求LCA

LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。 ———来自百度百科

**

LCA模板

**

以后遇到LCA问题可以直接拿来用
时间复杂度:预处理(nlogn)每次询问(logn)

#pragma GCC optimize(3,"Ofast","inline")  	//G++
#pragma comment(linker, "/STACK:102400000,102400000")
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#include <functional>
#define TEST freopen("in.txt","r",stdin);
#define mem(a,x) memset(a,x,sizeof(a))
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
typedef long long ll;
typedef unsigned long long ull; // %llu
const double PI = acos(-1.0);
const double eps = 1e-6;
const int mod=1e9+7;
const int INF = -1u>>1;
const int maxn = 5e5+5;
int n,m,root;
int depth[maxn],father[maxn][30],lg[maxn],len[maxn];
//depth为节点深度  father[i][j]为i节点向上跳2^i时所在的节点  len节点为到根节点的距离
//主要说一下 lg[x]为x在2进制下的位数     比如lg[5]=3,也就是101。所以我们第一步跳,只需要从2^(lg[5]-1)开始跳 (注意需要减1) 最开始能跳最大的步数就是2的n次方 这个n就是位数减1,仔细想想
//第一次跳4 (2进制下为100) 第二次不跳 第三次跳1(001) 即100+1=101可以优化时间
struct node
{
    int to,len;
};
vector<node>edge[2*maxn];
void add(int x,int y,int len)
{
    edge[x].push_back(node{y,len});
    edge[y].push_back(node{x,len});
}//建边
void dfs(int u,int fa,int lenth)
{
    father[u][0]=fa;        //father[u][0]就是u跳一个节点 也就是u的父亲节点
    depth[u]=depth[fa]+1;   //儿子节点的深度为父亲节点的深度加1
    len[u]=len[fa]+lenth;   //到根的长度
    for(int i=1; (1<<i)<=depth[u]; i++) //关键  大概意思就是 u跳2^i步等于u跳2^(i-1)步在跳2^(i-1)步
    {
        father[u][i]=father[father[u][i-1]][i-1];
    }
    for(int i=0; i<edge[u].size(); i++) //深搜下
    {
        node v=edge[u][i];
        if(v.to!=fa)
            dfs(v.to,u,v.len);
    }
}
int lca(int x,int y)
{
    if(depth[x]<depth[y])       //保证x的深度大于y
        swap(x,y);
//    for(int i=(int)(log2(n*1.0))+1;;i>=0;i--)         //这种做法可能会浪费一些时间,看题解写到了预处理lg感觉还巧妙
//    {
//        if(不在同一深度)
//            高深度向低深度跳
//        if(在同一深度)
//            跳之后的两个节点不同可以跳
//    }
    while(depth[x]>depth[y])
    {
        x=father[x][lg[depth[x]-depth[y]]-1];       //偷学而来
    }
    if(x==y)return x;           //如果深度相同时两个节点为同一个不用在向上搜了
    for(int i=lg[depth[x]]-1; i>=0; i--)    //深度不同时两个点同时向上搜 当向上搜到的节点不相等的时候证明可能到达 更新节点
    {
        if(father[x][i]!=father[y][i])
        {
            x=father[x][i];
            y=father[y][i];
        }
    }
    return father[x][0];        //返回最后到达节点的父亲
}
int main()
{
    // TEST
    ios;
    for(int i=1; i<=40000; i++)      //预先算出log_2(i)+1的值,用的时候直接调用就可以了,优化时间
        lg[i]=lg[i-1]+(1<<lg[i-1]==i);
    int T;
    cin>>T;
    while(T--)
    {
        cin>>n>>m;
        for(int i=1; i<=n-1; i++)
        {
            int x,y,len;
            cin>>x>>y>>len;
            add(x,y,len);
            root=x;
        }
        dfs(root,0,0);      //随便用一个点设为根节点
        while(m--)
        {
            int x,y;
            cin>>x>>y;
            int ans=len[x]+len[y]-2*len[lca(x,y)];      //两点间距离就是x到根节点的距离加上y到根节点的距离减去2倍的lca到根节点的距离
            cout<<ans<<"\n";
        }
        mem(len,0);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值