codevs —— 2370 小机房的树 倍增LCA

题目连接:戳我

题目大意,给定一个树,树上的边有权值。然后下面一个m,m次询问,每次询问两个点,然后对于每个询问输出他们的距离。

一直没找到好的LCA裸题,别人推荐给我一个,于是乎做了一下,处理father数组的同时,记录一个S数组,保存从i点网上跳2的j次方的路径上的权值和,查询的时候再求一下ans就可以了。如果看不懂的话,在代码里面尽量会说明的…………

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int size = 100010;
int fat[size][32];
int s[size][32];
int deep[size];
int eto[size],edist[size],head[size],next[size],tot;

int n;
void build(int f,int t,int d)   //建树函数,同建图一样建树。 
{
    eto[++tot] = t;
    edist[tot] = d;
    next[tot] = head[f];
    head[f] = tot;
}

void dfs(int u,int d) 
{
    /*
        dfs,第一个参数表示当前节点,第二个参数表示深度 
    */
    int tmp = deep[u] = d;  //深度数组,tmp可以去掉,是我调试的时候用的 
    for(int i = head[u];i;i = next[i])
    {
        int v = eto[i];
        if(!deep[v])    //如果v没有被访问过 
        {
            fat[v][0] = u;
            s[v][0] = edist[i];
            dfs(v,d+1);
            /*
                v向上跳2^0次方,也就是他的父亲,就是u,因为v是u的儿子,所以u是v的父亲
                从v向上跳1的距离就是这条边的权值
                然后递归处理他的儿子 
            */
        }
    }
}

inline void init()
{
    int k = log2(n);
    for(int j = 1;j <= k;j ++)
    {
        for(int i = 1;i <= n;i ++)
        {
            fat[i][j] = fat[fat[i][j-1]][j-1];
            s[i][j] = s[fat[i][j-1]][j-1]+s[i][j-1];
            /*
            利用倍增的思想,第一句话的意思就是,i向上跳2^j = i向上跳2^(j-1)再跳2^(j-1)次方
            第二句话的意思就是,i向上跳2^j的距离 = (i向上跳2^(j-1))(fat[i][j-1]的含义)向上跳(j-1)次方的距离+i向上跳2^(j-1)的距离 
            */
        }
    }
}

int find(int a,int b)
{
    if(deep[a] > deep[b])   swap(a,b);  //初始保证左边小,为了下面准备 
    int k = log2(n);
    int ans = 0;
    for(int i = k;i >= 0;i --)
    {
        if(deep[fat[b][i]] < deep[a])   continue;
        //上面意思是,如果跳后,深度小于a了,说明跳多了,我们的目的是让他们深度相等,所以不管他 
        ans += s[b][i];
        b = fat[b][i];
        //如果满足条件,ans加上跳的这段距离,更新b节点 
    }
    //此时他们深度相同 
    for(int i = k;i >= 0;i --)
    {
        if(fat[a][i] == fat[b][i])  continue;
        //如果他们跳后父亲相同了,这时候可能跳过了,我们也不管他 
        ans += s[a][i];
        ans += s[b][i];
        a = fat[a][i];
        b = fat[b][i];
        //如果满足条件,ans同时加上两边跳的距离,更新两个节点 
    }
    //这时候他们可能同一个点也可能不同点 
    if(a != b)
    {
        //如果不同点,那他们的父亲就是LCA,再跳一步即可,ans加上两边的权值,结束 
        ans += s[a][0];
        ans += s[b][0];
    }
    //返回答案 
    return ans;
}

int main()
{
    scanf("%d",&n);
    for(int i = 1;i < n;i ++)
    {
        int u,v,c;
        scanf("%d%d%d",&u,&v,&c);
        u ++;
        v ++;
        build(v,u,c);
        build(u,v,c);
    }
    dfs(1,1);
    init();
//  for(int i = 1;i <= n;i ++)  printf("deep[%d] : %d\n",i,deep[i]);
    int m;
    scanf("%d",&m);
    for(int i = 1;i <= m;i ++)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        l ++;
        r ++;
        int sum = find(l,r);
        printf("%d\n",sum);
    }
    return 0;
}

QAQ

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值