LCA-最近公共祖先

LCA-最近公共祖先

性质:

  • d(u,v)=d(u)+d(v)-2d(LCA(u,v))(d(i)表示到编号为i节点的深度)

  • 可以通过以上式子求两点之间的距离O(n)

    \[LCA(S\cup S')=LCA(LCA(S),LCA(S')) \]

  • LCA(u,v)必在u,v的最短路上

朴素法

int LCA(int x,int y)
{
    if(d[x]<d[y])//d[x]表示x结点的深度
    {			//此处表示将x表示较深的结点
        int t=d[x];
        d[x]=d[y];
     d[y]=t;
    }
      while (d[x] != d[y])//此处表示将两者调整到同一层
    {
        x = father[x];
    }
    while(x!=y)	//两点都向上搜索直到一致
    {
        x=father[x];
        y=father[y];
    }
    return x;
}

倍增法

\[ f[x][i] = f[f[x][i - 1]][i - 1]; \]

其中x表示当前节点,i表示x前2^i个节点(父节点)

因为2i=2(i-1)+2^(i-1)

x前第2i个节点为:x的前第2(i-1)个节点的前第2^(i-1)节点

这样可以把第前第i个节点用i-1的递推式来表达

#include <bits/stdc++.h>
using namespace std;
vector<int> v[40005];
vector<long long> dis[40005];
int d[40005], f[40005][25];//防止超出后数组越界我们把数组开大点
long long l[40005];//此算法模板是设根节点深度为1情况下的模板,但是根的深度设置为0或1的区别不影响LCA结果
//O(nlogn)
void dfs(int x, int father)
{
    d[x] = d[father] + 1;//每次搜索确定该节点的深度
    for (int i = 1; (1 << i) <= d[x]; i++)//i=1是为了从i-1==0开始推导,每次进入一层新dfs前都会先初始化
    {										//初始化f[x][0]
  
        f[x][i] = f[f[x][i - 1]][i - 1];//从i-1==0开始递推,一直推到到2^i>d[x],即推出x的前第2^i个节点
        								//直到超出根节点的位置,那么超出不会越界或者错误吗?不会,推出的超出部分即比根节点还高的节点都为0(memset->0),我们设0为根节点的父节点        											
    }
    for (int i = 0; i < v[x].size(); i++)//开始遍历下一级的节点
    {
        int next = v[x][i];
        if (next != father)
        {
            f[next][0] = x;//初始化下一级节点的上级节点为当前节点
            l[next] = l[x] + dis[x][i];//通过当前节点推出下级节点到根节点的距离(是权值的和不是深度)
            dfs(v[x][i], x);//深度优先搜索下一级节点
        }
    }
}
//O(logn)
int LCA2(int x, int y)
{
    if (d[x] < d[y])
    {
        swap(x, y);//令x为较深节点
    }
    for (int i = 20; i >= 0; i--)//logn为i的初始值,可按照题意调整
    {
        if (d[f[x][i]] >= d[y])//当x的前第2^i个节点比y深时,将x点移动到改节点,因为1~2^n中的
        {
            x = f[x][i];
        }
        if (x == y)//该句只能放在上面那个if语句的下面,保证x变化后的值都会被判断,以防万一当i==0时x变化了但没有下一i此判断,然后x==y时不会进入上面那个语句,因为如果会进入上面的语句y!=x
            return x;
    }
    //此处是从"远处"试探到"近处",x,y前2^i个节点不一致就跳到该这两个结点,
    //求"最远"的,两节点不一致的结点,即,公共节点与非公共结点的边界
    for (int i = 20; i >= 0; i--)//能用倍增法,且用一层循环就实现因为:1.每个正整数i(此处是第i个父节点)
    {							//都能用2^x的和表示,2. 2的某个次方只出现一次(因为能出现两次以上就能用更大的数来表示,而能用更大数表示的在之前按已经循环到了3.且x,y跳跃后的值,再跳跃到非公共结点位置所需的i比从前一个结点跳到当前小,原因同上)
        if (f[x][i] != f[y][i])
        {
            x = f[x][i];
            y = f[y][i];
        }
    }
    return f[x][0];
}
int main()
{
    int t;
    int n, m;
    scanf("%d", &t);
    while (t--)
    {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n - 1; i++)//O(n)
        {
            int a, b;
            long long k;
            scanf("%d%d%lld", &a, &b, &k);
            v[a].push_back(b);
            v[b].push_back(a);
            dis[a].push_back(k);
            dis[b].push_back(k);
        }
        l[1] = 0;//初始化l[1],d[0]
        d[0] = 0;
        dfs(1, 0);
        for (int i = 1; i <= m; i++)
        {
            int a, b;
            scanf("%d%d", &a, &b);
            int lca = LCA2(a, b);
            // cout << lca << endl;
            printf("%lld\n", l[a] + l[b] - 2 * l[lca]);
        }
    }
    return 0;
}
posted @ 2021-10-21 22:14  多巴胺不耐受仿生人  阅读(72)  评论(0)    收藏  举报