求最近公共祖先(LCA)板子 x

LCA目前比较流行的算法主要有tarjian,倍增和树链剖分

1)tarjian

是一种离线算法,需要提前知道所有询问对

算法如下

  1.读入所有询问对(u,v),并建好树(建议邻接表)

  2.初始化每个节点各属一个并查集,都指向自己

  3.对整棵树进行dfs(深度优先搜索)遍历

每处理到一个新节点(u)时看他的另一半(询问对象v)是否visit过,如果visit过了,则这组询问对的lca即v的并查集的根节点,若没有visit过,则继续向下深搜,该节点记为已visit

每当回溯的时候都将子节点的并查集并到父节点的并查集中

这样一遍走下来就完成了tarjian算法。

超详细tarjain:orz

2)树上倍增

f[i,j]表示i的第2^j祖先dfs预处理f[i,j]=f[f[i,j-1],j-1];

对于每一对x,y先将深度调成一样再枚举j逐一往上找,这两个过程都是log的

超详细树上倍增:orz

3)树剖

树剖(树链剖分)是一种在线算法,跑起来非常快,应该是目前LCA算法中最优的

建树后,我们需要把整棵树划为轻重链,

每一个非叶子节点都一定在一条重链上

定义:

重边:父节点与其子树最大(子节点最多)的节点的连边称为重边

轻边:非重边即为轻边

重链:相连的重边称为重链

划分重链后,我们要记一个jump数组表示存每个节点的“跳”的信息

如果这个节点在重链上,则jump[i]为它所属重链的根节点(最顶端)

如果这个节点不在重链上或者它是一条重链的顶端(根节点),那么jump[i]为它的父节点

接下来我们就可以处理询问对了

比如求两个节点a,b的LCA

我们先看他们是否在同一条重链上,如果是,则LCA即为深度较小的节点

如果不是,则我们需要比较jump[a]和jump[b]的深度,jump[a]比较浅则令a=jump[a]反之令b=jump[b]

重复以上过程直到a==b(LCA为这个节点)或a,b在同一条重链上时(LCA为深度浅的节点)

这样就完成了,复杂度虽说评是O(n*logn)但实际上跑起来快得多

超详细树剖:orz

练手题:

洛谷 P3379 【模板】最近公共祖先(LCA)

题目描述

如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

输入输出格式

输入格式:

第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。

接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。

输出格式:

输出包含M行,每行包含一个正整数,依次为每一个询问的结果。

输入输出样例

输入样例#1:

5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5

输出样例#1:

4
4
1
4
4

说明

时空限制:1000ms,128M

数据规模:

对于30%的数据:N<=10,M<=10

对于70%的数据:N<=10000,M<=10000

对于100%的数据:N<=500000,M<=500000

样例说明:

该树结构如下:

第一次询问:2、4的最近公共祖先,故为4。

第二次询问:3、2的最近公共祖先,故为4。

第三次询问:3、5的最近公共祖先,故为1。

第四次询问:1、2的最近公共祖先,故为4。

第五次询问:4、5的最近公共祖先,故为4。

故输出依次为4、4、1、4、4。

LCA板子!!!

tarjian还没写.

1)树上倍增

#include <iostream>
#include <cstdio>
#include <cmath>
//maybe my English is not very good 

using namespace std;

const int M = 5e5 + 1;
int n,m,s;
int num;
int deep[M],h[M];
bool vs[M];
int jumps[M][21];
int p;

struct A{
    int next;
    int to;
}t[M<<1];

inline int read() //optimize
{
    int x=0,f=1;char ch=getchar();

    while(ch<‘0‘||ch>‘9‘)
    {
        if(ch==‘-‘) f=-1;
        ch=getchar();
    }

    while(ch>=‘0‘&&ch<=‘9‘)
    {
        x=x*10+ch-‘0‘;
        ch=getchar();
    }

    return x*f;
}

void ADD(int x,int y) //connect the x and the y
{
    num++;
    t[num].to=y;
    t[num].next=h[x];
    h[x]=num;
}

void Dfs(int u)
{
    for(int i=h[u];i!=-1;i=t[i].next)
    {
        int v=t[i].to; //u‘s next side
        if(deep[v] == 0) //if v is not visited
        {
            deep[v]=deep[u]+1; //deep+1
            jumps[v][0]=u; //u is v‘s dad
            Dfs(v); //continue Dfs
        }
    }
}

void steps()
{
    p=int(log(n)/log(2)+0.001); //find the biggest
    for(int i=1;i<=p;i++) //the Limit
        for(int j=1;j<=n;j++)
            jumps[j][i]=jumps[jumps[j][i-1]][i-1];
//the j jump 2^i can get to the (first jump 2^(i-1),then jump 2^i-1 can get to)
//eh...I will speak in Chinese.
//because 倍增 is use 次方的形式 increase
}

int LCA(int a,int b)
{
    //We let the b‘s deep is small
    if(deep[a]<deep[b]) swap(a,b);
    for(int i=p;i>=0;i--)
    {//first let the a jump to the b‘s deep
        if(deep[jumps[a][i]]>=deep[b])
        a=jumps[a][i];
    }
    if(a == b) return b; //if the b is them‘s LCA , return b
    for(int i=p;i>=0;i--) //jump together
    {
        if(jumps[a][i]!=jumps[b][i])
        a=jumps[a][i],b=jumps[b][i]; //update
    }
    return jumps[a][0];
}

int main()
{
    //s is the root
    n=read();m=read();s=read();
    for(int i=1;i<=n;i++) h[i]=-1;
    int x,y;
    for(int i=1;i<n;i++)
    {
        x=read();y=read();
        //connect the x and the y
        ADD(x,y);
        ADD(y,x);
    }
    deep[s]=1; //this is too important !!!
    //if you don‘t think so ,"//" it.
    //and then you will know
    Dfs(s); //Dfs the root(s)
    steps(); //find the steps
    int a,b;
    while(m--)
    {
        a=read();b=read();
        printf("%d\n",LCA(a,b));
    }
    return 0;
}

树上倍增英文版???

#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<stdio.h>
#include<vector>
#define maxn 500500
using namespace std;
///隶属邻接表
struct Edge{                    //邻接表的结构体
    int from,to;
}edges[2*maxn];                 //边要乘2,因为是无向图 ;
int first[maxn],next[2*maxn];   //同理;
int read(){                        //读入优化,可以照着这个模板来写,这个还算写的比较好看。
    int re=0;
    char ch=getchar();
    while (ch<‘0‘ || ch>‘9‘) ch=getchar();
    while (ch>=‘0‘ && ch<=‘9‘){
        re=re*10+ch-‘0‘;
        ch=getchar();
    }
    return re;
}
///////////////////////////////////////////////
///全局变量
int n,m;
int root;
int height[maxn];
float log2n;
///////////////////////////////////////////////////////
///隶属LCA的全局变量
int f[maxn][20];//
int have[maxn];                           //have,有没有找过,这都是套路 。
void dfs(int u,int h){                 //u代表点的标号,h代表高度。
    int v;
    height[u]=h;
    for(int i=1;i<=log2n;i++) {
        if(h<=(1<<i)) break;              //由于i是从小到大计算的,故(1<<i)>=h 时可直接退出。请务必想清楚是<=  还是=。
        f[u][i] = f[ f[u][i-1] ][i-1]; //动规计算。同样也是一切倍增算法的核心。
    }
    int k=first[u];
    while(k!=-1){
        v=edges[k].to;
        if(!have[v]) {
            have[v]=1;
            f[v][0]=u;                 //将要找的下一个点的父节点标为当前处理的节点u。
            dfs(v,h+1);
        }
        k=next[k];
    }
}
int require_LCA(int a,int b){
    int da=height[a],db=height[b];
//第一步,将a,b两点移到同样的高度,只动高度大的那个点而不动高度小的那个点。
    if(da!=db) {
        if(da<db){                   //保证a的高度是大于b的高度的。
            swap(a,b);
            swap(da,db);
        }
        int d=da-db;
        for(int i=0;i<=log2n;i++)
            if( (1<<i) & d) a=f[a][i]; //这里的位运算可以减少代码量
                                       //考虑到d是一个定值,而(1<<i)在二进制中只有第(i+1)位是1;
                                       //那么d与(1<<i)如果某一位为1,那么表示可以向上移动,
                                       //如果此时不移动,那么i增大了后就无法使height[a]==height[b]了
    }
//第二步,找到某个位置i,在这个位置时,f[a][i]!=f[b][i],但再向上移动一步,a,b相同了
//从log2n开始从大到小枚举i,如果超过了a,b的高度,则令i继续减小
//如果没有超过a,b的高度,那么就判断移动了后会不会让a==b,
//是,则i继续减小,否则,令此时的a=f[a][i],b=f[b][i];
    if(a==b) return b;
    int i=0;
    for(i=log2n;i>=0;i--) {
        if(height[ f[a][i] ]<0) continue;
        if( f[a][i]==f[b][i] ) continue;
        else a=f[a][i],b=f[b][i];        //顺便一提,在第二步任何地方没有break;
                                       //我就是因为在这里写了一个break,然后找了我两个小时啊。
    }
    return f[a][0];
}
/////////////////////////////////
///据说从主函数开始阅读是个好习惯。
int main(){
//    freopen("in2.txt","r",stdin);
    n=read();m=read();root=read();
    memset(first,-1,sizeof(first));
    memset(next,-1,sizeof(next));
    int s,t;
    int dsd=2*(n-1);
    for(int i=1;i<=dsd;i+=2) {
        s=read();t=read();      //读入优化。
        edges[i].from=s;
        edges[i].to=t;
        edges[i+1].from=t;
        edges[i+1].to=s;
        next[i]=first[s];
        first[s]=i;
        next[i+1]=first[t];
        first[t]=i+1;
    }
    // 以上是邻接表,在此不再赘述。
    log2n=log(n)/log(2)+1;        //C++计算log是自然对数,我们要用的以2为底的对数,故要除以log(2);
                                  //对无理数加上1或是0.5是个好习惯,可以减小误差;
    memset(have,0,sizeof(have));
    memset(height,0,sizeof(height));
    memset(f,-1,sizeof(f));
    have[root]=1;                //fa[][]和height[]要在dfs理进行计算,不然根本找不到某个非根节点的父亲是谁;
    dfs(root,1);
    for(int i=1;i<=n;i++){
        for(int j=0;j<=log2n;j++) {
            if(height[i] <=(1<<j) ) break;
        }
    }
    for(int i=0;i<m;i++) {      //应对要求进行求解。
        s=read();t=read();
        int y=require_LCA(s,t);
        printf("%d\n",y);
    }
    return 0;
}

中文版23333

2)树剖

#include <iostream>
#include <cstdio>
#define _(ch) ch=read()                    //便于读入 

using namespace std;

const int S = 500001;
bool f[S];                                 //dfs 标记
int n,m,s;
int fa[S];                                 //并查集
int num,h[S];                             //邻接表
int deep[S];                             //深度
int sum[S];                             //子结点个数
int dad[S];                             //链头元素 

struct B{
    int to,next;
}t[S<<1];

inline int read()                         //optimize
{
    int x=0,f=1;char ch=getchar();

    while(ch<‘0‘||ch>‘9‘)
    {
        if(ch==‘-‘) f=-1;
        ch=getchar();
    }

    while(ch>=‘0‘&&ch<=‘9‘)
    {
        x=x*10+ch-‘0‘;
        ch=getchar();
    }

    return x*f;
}

void ADD(int x,int y)                     //connect the x and the y
{
    num++;
    t[num].to=y;
    t[num].next=h[x];
    h[x]=num;
}

inline int Find(int x)
//find the root (重链‘s top)
{return fa[x] == x ? x : fa[x] = Find(fa[x]);}

inline void Unions(int a,int b)
{                                        //union(搭重链)
    /*int f1=Find(a);
    int f2=Find(b);
    if(f1!=f2)
    {
        fa[f1]=f2;
    }*/
    fa[Find(b)]=Find(a);
}

inline void dfs(int p)//D is the (结点)
{//calc every D‘s son D
//每个结点的深度
    f[p]=true;
    int maxx=0;                            //寻找子节点中拥有子结点个数最多的节点编号
    sum[0]=-1;                            //0号没有子结点
    for(int j=h[p];j;j=t[j].next)
    {                                    //进行遍历
        int v=t[j].to;
        if(f[v]) continue;
        deep[v]=deep[p]+1;
        dad[v]=p;                        //p is v‘s dad
        dfs(v);                         //continue dfs
        if(sum[v] > sum[maxx]) maxx=v;     //update
        sum[p]+=sum[v]+1;
//        p的子结点数 = p 的以‘v‘为根的子树的结点数目加上‘v‘这个点(即+1)
    }
    if(maxx) Unions(p,maxx);            //if updated
    //that means find the (重链) succeed
}

inline int jump(int p)                     //find p can jump to
{
    int top=Find(p);                     //(重链)‘s top
    if(top == p) return dad[p];
//    如果p所处于的链的链头就是自己,也就是说,已经位于链的top处,所以只能够跳到他的父结点的位置,
//    所以直接return it‘s dad,即跳一步到达父结点处
//    说白了就是说,一定要跳!!!
    return top;                            //其余情况就返回链头就好(就是当前结点跳到了链头位置)
}

inline int Lca(int a,int b)             //Lca
{
    while(a!=b)                         //当两点不相等的时候就开始跳
    {
        if(Find(a)==Find(b))             //如果它们位于同一条重链上
          return deep[a]<deep[b] ? a:b; //直接返回深度较浅的那个点
        int ja=jump(a),jb=jump(b);
        if(deep[ja] > deep[jb])            //如果a跳了之后没有到达b跳了之后的深度
            a=ja;                        //就选取深度较深的点跳
        else
            b=jb;
    }
    return a;
}

int main()
{
    _(n),_(m),_(s);
    int x,y;
    for(int i=1;i<n;i++)
    {
        _(x),_(y);
        ADD(x,y),ADD(y,x);
        fa[i]=i;                        //顺便初始化一下并查集
    }
    fa[n]=n;                             //有一个没有进行初始化的并查集,进行初始化
    deep[s]=1;                             //根结点的深度设置为1,非常重要!!!!
    dfs(s);                                //寻找子节点个数,位于哪一条重链上
    while(m--)
    {
        _(x),_(y);
        printf("%d\n",Lca(x,y));
    }
    return 0;
}

混杂版(才不是英语不好!)

时间: 2024-10-17 21:01:14

求最近公共祖先(LCA)板子 x的相关文章

【LCA求最近公共祖先+vector构图】Distance Queries

Distance Queries 时间限制: 1 Sec  内存限制: 128 MB 题目描述 约翰的奶牛们拒绝跑他的马拉松,因为她们悠闲的生活不能承受他选择的长长的赛道.因此他决心找一条更合理的赛道.此题的输入于第一题相同,紧接着下一行输入一个整数K,以后K行为K个"距离问题".每个距离问题包括两个整数,就是约翰感兴趣的两个农场的编号,请你尽快算出这两地之间的距离. N个点,N-1条边 输入 第1行:两个分开的整数:N和M: 第2..M+1行:每行包括4个分开的内容,F1,F2,L,

用“倍增法”求最近公共祖先(LCA)

1.最近公共祖先:对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u.v的祖先且x的深度尽可能大.2.朴素算法:记录下每个节点的父亲,使节点u,v一步一步地向上找父亲,直到找到相同的“祖先”,即 是所求的答案,时间复杂度O(n).3.优化算法(倍增法):利用二进制的思想,想办法使一步一步向上搜变成以2^k地向上跳. 所以定义一个P[][]数组,使p[i][j]表示节点i的2^j倍祖先,因此p[i][0]即为i的父亲. 我们可以得到一个递推式p[i][j]=p

最近公共祖先(LCA)问题

描述 对于有根树T的两个节点u和v,最近公共祖先LCA(T,u,v)表示一个节点x满足x是u,v的公共祖先且x的深度尽可能大. 算法 求解LCA问题主要有三种解法,分别是暴力搜索,Tanjar算法,最后一种是转化为RMQ问题,用DFS+ST算法来求解 暴力搜索 如果数据量不大的时候可以采用暴力搜索法.先将节点u的祖先节点全部标记出来,然后顺着节点v沿着父亲节点的方向向上遍历,直到遍历到一个被标记的节点,这个节点即为所求节点.或者分别获取u,v到根节点的路径P1,P2,可以将这两条路径看做两个两个

POJ 1470 Closest Common Ancestors【最近公共祖先LCA】

题目链接:http://poj.org/problem?id=1470 题目大意:给出一棵树,再给出若干组数(a,b),输出节点a和节点b的最近公共祖先(LCA) 就是很裸的LCA,但是我用的是<挑战程序设计竞赛>上的"基于二分搜索的算法求LCA",我看网上用的都是tarjan算法.但是我的代码不知道为什么提交上去 wrong answer,自己想的很多测试数据也都和题解结果一样,不知道错在哪里,所以把代码保存一下,留待以后解决...... 如果读者有什么建议,希望提出来,

最近公共祖先 LCA 倍增算法

倍增算法可以在线求树上两个点的LCA,时间复杂度为nlogn 预处理:通过dfs遍历,记录每个节点到根节点的距离dist[u],深度d[u] init()求出树上每个节点u的2^i祖先p[u][i] 求最近公共祖先,根据两个节点的的深度,如不同,向上调整深度大的节点,使得两个节点在同一层上,如果正好是祖先结束,否则,将连个节点同时上移,查询最近公共祖先. void dfs(int u){ for(int i=head[u];i!=-1;i=edge[i].next){ int to=edge[i

最近公共祖先LCA(Tarjan算法)的思考和算法实现——转载自Vendetta Blogs

最近公共祖先LCA(Tarjan算法)的思考和算法实现 LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 小广告:METO CODE 安溪一中信息学在线评测系统(OJ) //由于这是第一篇博客..有点瑕疵...比如我把false写成了flase...看的时候注意一下! //还有...这篇字比较多 比较杂....毕竟是第一次嘛 将就将就 后面会重新改!!! 首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先

LCA 在线倍增法 求最近公共祖先

第一步:建树  这个就不说了 第二部:分为两步  分别是深度预处理和祖先DP预处理 DP预处理: int i,j; for(j=1;(1<<j)<n;j++) for(int i=0;i<n;++i) if(fa[i][j]=-1) fa[i][j]=fa[fa[i][j-1]][j-1];/*DP处理出i的2^j祖先是谁*/ 深度预处理: 1 void dfs(int now,int from,int deepth) 2 { 3 deep[now]=deepth; 4 for(i

最近公共祖先 LCA Tarjan算法

来自:http://www.cnblogs.com/ylfdrib/archive/2010/11/03/1867901.html 对于一棵有根树,就会有父亲结点,祖先结点,当然最近公共祖先就是这两个点所有的祖先结点中深度最大的一个结点. 0 | 1 /   \ 2      3 比如说在这里,如果0为根的话,那么1是2和3的父亲结点,0是1的父亲结点,0和1都是2和3的公共祖先结点,但是1才是最近的公共祖先结点,或者说1是2和3的所有祖先结点中距离根结点最远的祖先结点. 在求解最近公共祖先为问

最近公共祖先(LCA)

by mps Define:求树上两个点的祖先中里两个点最近的一个点,该点称为这两个点的最近公共祖先(英译LCA<Lowest Common Ancestors>). 那么,如何求LCA呢? 经过思考,不难发现,有一种暴力方法,我们对于这两个点不断BFS,直到出现一个相同的点,该点即为LCA,空间如果跟不上的话可以改为迭代加深搜索 时间复杂度O(N2),对于大一点的数据,会TLE的,我们追求完美,还有更好的算法. 算法改进 我们可以先求出两个节点在这颗树中的深度(D1,D2),然后由偏低的上升