【OI】倍增求LCA

╭(′▽`)╯

总之,我们都知道lca是啥,不需要任何基础也能想出来怎么用最暴力的方法求LCA,也就是深度深的点先跳到深度浅的点的同一深度,然后一起向上一步步跳。这样显然太慢了!

所以我们要用倍增,倍增比较屌,直接2^k速度往上跳,而且复杂度和树剖lca差不多,那么步骤分为两步

1.让两个点到同一深度

2.到了同一深度同步往上跳

反正我一开始看的时候一直在想,万一跳过了怎么办?哈哈哈,所以说我们有办法嘛:

定义deepv为v点的深度,设两个要求lca的点分别为a,b,且deepa >= deepb

所以,枚举找出最大的k使2^k <= deepa,这就是最大的跳的距离;

接着让他们到达同一深度:

从大到小枚举k,如果 deepa - 2^k >= deepb就往上跳2^k步,因为如果跳了2^k步的话一定deepa >= deepb

所以,我们跳的第一步一定是能跳的最大的一步,所以接下来只能跳次大的一步,同理跳完之后deepa >= deepb

......

因为k是越来越小的,k = 0的时候2^k = 1,因此无论如何最后都会以最大的效率跳到相同的深度

现在跳到了相同的深度,然后要同时向上走找到lca。

假设跳了 2 ^ k步之后它们到的位置不相等,说明lca还在深度更浅的地方,因为如果跳之后到的位置相等了,显然这个位置一定在lca的上面

所以,只要判断跳了 2 ^ k步后它们的位置如果不相等,就跳这步,这样就保证了跳到的深度一定小于lca,最后k = 0时 2 ^ k = 1,

则枚举完了k,它们所在的深度显然一定是lca的深度-1,则lca就是它们任意一个的父亲。

代码(luogu lca模板):

#include <cstdio>
#include <vector>
#include <cstring>

const int MaxN = 500010;

int n,m,s;
int par[MaxN][30];
int deep[MaxN];
bool vis[MaxN];

struct Edge{
    int to,nxt;
}e[MaxN*2];
int head[MaxN];
int cnt;

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

void getdeep(int u){
    vis[u] = 1;
    for(int i = head[u]; i; i = e[i].nxt){

        int to = e[i].to;
        if(to == u || vis[to]) continue;

        par[to][0] = u;

        deep[to] = deep[u] + 1;

        getdeep(to);

    }

}

void getpar(){
    for(int up = 1; (1<<up) <= n; up++){
        for(int i = 1; i <= n ; i++){
            par[i][up] = par[par[i][up-1]][up-1];
        }

    }

}

int lca(int u,int v){
    if(deep[u] < deep[v] ) std::swap(u,v);

    int max_jump = -1;

    while(1<<(max_jump+1) <= deep[u]) max_jump++;

    for(int i = max_jump; i >= 0; i--){
        if(deep[u] - (1<<i) >= deep[v]){
            u = par[u][i];
        }

    }

    if(u == v)
        return u;

    for(int i = max_jump; i >= 0; i--){
        if(par[u][i] != par[v][i]){
            u = par[u][i];
            v = par[v][i];

        }
    }

    return par[u][0];

    return 0;

} 

int main()
{
    scanf("%d%d%d",&n,&m,&s);

    for(int i = 1; i < n; i++ ){
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        add(v,u);
        //par[v][0] = u;
        //par[u][0] = v;
    }

    deep[s] = 0;

    getdeep(s);

    getpar();

    for(int i = 1; i <= m; i++){
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%d\n",lca(a,b));

    }

    //par[i][j] = par[par[i][j-1]][j-1]

    return 0;
}

原文地址:https://www.cnblogs.com/dudujerry/p/11622916.html

时间: 2024-08-27 09:21:06

【OI】倍增求LCA的相关文章

HDU 4081 Qin Shi Huang&#39;s National Road System 最小生成树+倍增求LCA

原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=4081 Qin Shi Huang's National Road System Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 5428    Accepted Submission(s): 1902 Problem Description

【LCA】倍增求LCA

题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每行包含两个正整数x.y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树). 接下来M行每行包含两个正整数a.b,表示询问a结点和b结点的最近公共祖先. 输出格式: 输出包含M行,每行包含一个正整数,依次为每一个询问的结果. 输入输出样例 输入样例#1: 复制 5 5 4 3 1 2 4

hdu 2586 How far away ? 倍增求LCA

倍增求LCA LCA函数返回(u,v)两点的最近公共祖先 #include <bits/stdc++.h> using namespace std; const int N = 40010*2; struct node { int v,val,next; node(){} node(int vv,int va,int nn):v(vv),val(va),next(nn){} }E[N]; int n,m; int tot,head[N],dis[N],f[N][20],dep[N]; void

POJ 1986:Distance Queries(倍增求LCA)

http://poj.org/problem?id=1986 题意:给出一棵n个点m条边的树,还有q个询问,求树上两点的距离. 思路:这次学了一下倍增算法求LCA.模板. dp[i][j]代表第i个点的第2^j个祖先是哪个点,dp[i][0] = i的第一个祖先 = fa[i].转移方程:dp[i][j] = dp[dp[i][j-1][j-1]. 1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm&g

tarjan,树剖,倍增求lca

1.tarjan求lca Tarjan(u)//marge和find为并查集合并函数和查找函数 { for each(u,v) //访问所有u子节点v { Tarjan(v); //继续往下遍历 marge(u,v); //合并v到u上 标记v被访问过; } for each(u,e) //访问所有和u有询问关系的e { 如果e被访问过; u,e的最近公共祖先为find(e); } } 2.倍增lca(在线) #include<bits/stdc++.h> using namespace st

[luogu3379]最近公共祖先(树上倍增求LCA)

题意:求最近公共祖先. 解题关键:三种方法,1.st表 2.倍增法 3.tarjan 此次使用倍增模板 #include<cstdio> #include<cstring> #include<algorithm> #include<cstdlib> #include<iostream> #include<cmath> using namespace std; typedef long long ll; int n,m,root,cnt

(贪心+倍增求LCA) hdu 4912

Paths on the tree Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others)Total Submission(s): 1097    Accepted Submission(s): 366 Problem Description bobo has a tree, whose vertices are conveniently labeled by 1,2,…,n. T

倍增求LCA

LCA:(Least Common Ancestors),即最近公共祖先节点. 1 const int maxn = 100003; 2 const int maxl = 19; 3 4 struct Edge { 5 int t; 6 Edge *ne; 7 }; 8 9 int dep[maxn], up[maxn][maxl]; 10 Edge *head[maxn]; 11 12 void DFS(int u) { 13 for (int i = 1; i < maxl; ++ i) {

CF 519E(树上倍增求lca)

传送门:A and B and Lecture Rooms 题意:给定一棵树,每次询问到达点u,v距离相等的点有多少个. 分析:按情况考虑: 1.abs(deep[u]-deep[v])%2==1时,必定不存在到达u,v距离相等的点. 2.如果deep[u]==deep[v]时,ans=n-num[lca(u,v)u在的儿子树]-num[lca(u,v)v在的儿子树]. 3.如果deep[u]!=deep[v]时,在u到v的路径中找出到达u,v相等的节点x,则ans=num[x]-num[u在的