一个蒟蒻的挣扎LCA (倍增)

Posted 莳萝萝

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个蒟蒻的挣扎LCA (倍增)相关的知识,希望对你有一定的参考价值。

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
struct edge{
    int next,to;
    edge(){
    }
    edge(int a,int b){
        next=a; to=b;
    }
}E[10004];//建一个图…… 
int f[100001][31],dep[100001],first[100001],tot;
int n,m,root;
void add_to_edge(int x,int y)
{
    E[++t]=edge(first[x],y);
    first[x]=t;
}//连边(无向图连两次哦) 
void dfs(int x,int fa)
{
    f[x][0]=fa;
    dep[x]=dep[fa]+1;
    //初始化 , 它的深度是它父亲的深度+1,x 往上倍增 2^0 层 是它的父亲 
    int k=ceil(log(dep[x])/log(2));//倍增上限 
    for (int i=1; i<=k; i++)
    {
        f[x][i]=f[f[x][i-1]][i-1];
    }//f数组存预处理的值,f[x][i]存的是x向上倍增 
    for (int i=first[x]; i; i=E[i].next)
    {
        int pos=E[i].to;
        if (pos!=fa)//防止死循环,由于是存了两次所以E[i].to会连向它的父亲 
        dfs(pos,x);
    }
}//预处理 
int n,m,root;
void LCA()
{
    if (dep[x]<dep[y]) swap(x,y);
    int k1=dep[x]-dep[y];
    int k2=ceil((log(n))/log(2));
    for (int i=0; i<=k2; i++)
    {
        if (k1&(1<<i))
        x=fa[x][i];
    }//向上跳!! 
    if (x==y) return x;//两者在同一层并且相等那么x 就是它们的共同祖先 
    int k3=(log(dep[x])/log(2));
    for (int i=k3; i>=0; i--)
    {
        if (f[x][i]!=f[y][i])
        {
            x=f[x][i]; y=f[y][i];
        }//倍增 
    }
    return f[x][0];
}
int main()
{
    cin>>n>>m>>root;
    for (int i=1; i<=m; i++)
    {
        int x,y;
        cin>>x>>y;
        add_to_edge(x,y);
        add_to_edge(y,s); 
    }
    dfs(root,0);
    
}
View Code

 

还是济南集训的内容,让人头秃(不得不说两个老师讲了两遍我勉勉强强才搞懂一点点)

 


首先来看:

LCA的含义

Least  Common  Ancestors

LCA就是最近公共祖先,至于它的含义,我觉得例题写的看起来会更清楚,请看:

 

 好的,明白了它的含义后,我们很容易想到朴素算法:

询问(x,y)的最近公共祖先,可以先向上枚举x的所有祖先,保存在数组Anc[]中。然后以相同的方法向上枚举y的所有祖先,当第一次发现有y的某个祖先k出现在Anc[]中,则输出k,算法结束。
 此时,每次查询复杂度为O( N )
(TLE警告哦)
那么其他方法呢,请看,这里介绍两种,树上倍增,与树链剖分(会在下一篇博客里写到)

树上倍增算法

核心思想:

  • 令F[x][n]表示x的2^n级祖先是谁.
  • 所以:F[x][n] = F[F[x][n – 1]][n – 1].
  • 对于两个点x, y.,求他们的LCA
  • 先把x, y提到同一高度.(方便向上进行倍增)
  • N从大到小枚举.(从高往低跳)
  • 查询F[x][n], F[y][n]是不是相等(比较倍增后的祖先,防止误判
  • 如果是的话说明n太大了,把n改小点.(最近公共祖先的祖先一定是他们的共同祖先
  • 不是的话就说明n不大,可以把x, y上移.(这个很容易理解吧)
 原理,如下图(图片来自老师的ppt(它其实应该有个动图然而我不知道动图怎么传)

 

 看明白思路了吗,思路还是可以懂得吧,那我们来看代码,理解代码基本就能写了!
(不过我之前也始终看不明白代码就是了,思路都懂代码不会打,我可真是个小垃圾哦)
#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
const int maxn = 500005;
const int maxe = 1000005;
int n,m,root;

struct line
{
    int from,to;
    line(){}//空构造函数 line p; 
    line(int A,int B){
        //构造函数 line L=line(1,2);
        from=A;to=B;
    }
}edge[maxe];
//上面是新建一个树


int last[maxn],_next[maxe],e; 
//last[x]表示以x为起点的最后一条边(的编号) 
//_next[i]表示与第i条边起点相同的上一条边(的编号) 

void add_edge(int x,int y)
{
    edge[++e]=line(x,y);
    _next[e]=last[x];
    last[x]=e;
}
//存边 

int Fa[maxn][35],Dep[maxn];

void dfs(int x,int fa)
{
    int i,k,y;
    Fa[x][0]=fa;//当前节点x的父亲节点fa 
    Dep[x]=Dep[Fa[x][0]]+1; //x的深度是它父亲节点的深度+1            
    //记录当前节点的深度 
    k=ceil(log(Dep[x])/log(2));  //ceil函数是向上取整        
    //x往上倍增的上限 
    for(i=1;i<=k;i++)Fa[x][i]=Fa[Fa[x][i-1]][i-1]; 
     //倍增计算祖先 ,记录 
    for(int i=last[x];i;i=_next[i])//枚举与x相邻的边 
    {
        int v=edge[i].to;
        if(v!=fa)dfs(v,x); 
    }
}

int LCA(int x,int y)
{
    int i,k,s;
    s=ceil(log(n)/log(2));                 //该树倍增最大可能的上限 
    if(Dep[x]<Dep[y])swap(x,y);      //交换x和y的值 
    /////////////x往上走k层,让x与y处于同一层 //////////
    k=Dep[x]-Dep[y];
    for(i=0;i<=s;i++)
        if(k&(1<<i))x=Fa[x][i]; 
    if(x==y)return x;                     //x==y时,x就是最近公共祖先 
    ///////////////////////////////////////////////////
    s=ceil(log(Dep[x])/log(2));           //计算向上倍增的上限 
    for(i=s;i>=0;i--)
        if(Fa[x][i]!=Fa[y][i]){ x=Fa[x][i]; y=Fa[y][i]; }
    return Fa[x][0];
}

int main()
{
    int i,j,k;
    cin>>n>>m>>root;
    for(i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add_edge(x,y);
        add_edge(y,x);//它是树,也就是无向图,所以存两次边 
    }
    dfs(root,0);//预处理 
    for(i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\\n",LCA(x,y));
    }
        
} 
代码(带注释) 

OK吗?

这里建议去练练板子,指路-> https://www.luogu.org/problem/P3379

(哇我居然可以写蓝题了哎!!可喜可贺)

先到这里,如有问题欢迎指正

感谢观看   ありがとうございます


 

以上是关于一个蒟蒻的挣扎LCA (倍增)的主要内容,如果未能解决你的问题,请参考以下文章

一个蒟蒻的挣扎简单算法

一个蒟蒻的挣扎单源最短路(Dijkstra)

蒟蒻的模板

10月21日 蒟蒻的流水账

蒟蒻的代码规范与文档编写规范

LCA及例题