ST算法是求最近公共祖先的一种 在线 算法,基于RMQ算法,本代码用双链树存树

  预处理的时间复杂度是 O(nlog2n)   查询时间是 O(1) 的

  另附上离线算法 Tarjan 的链接:

    http://www.cnblogs.com/hadilo/p/5840390.html


  首先预处理出深度,以及 DFS 序,这里的DFS序是指回溯时也算上,比如

 void dfs(int x,int dep)
{
int i;
d[x]=dep;
a[++top]=x;
for (i=down[x];i!=;i=next[i])
{
dfs(i,dep+);
a[++top]=x;
}
}

  然后记录每个节点在 DFS 序中第一次出现的位置,b[i] 为第 i 号节点第一次出现的位置

     for (i=;i<=top;i++) if (b[a[i]]==) b[a[i]]=i;

  开始 DP 处理区间区间内最小值,这里使用 RMQ 算法,其功能类似于线段树或树状数组

  f[i][j] 表示从第 i 位开始,连续 2j 个数的最小值,状态转移:

     f[i][j]=min(f[i][j-],f[i+(<<(j-))][j-])

  因为它是 2 的幂次方的状态,所以每次转移可以看做把当前状态分为两个相等的部分,求两部分的最小值

  如: 5 7 3 2   和   4 6 1 5

     min=2           min=1

 即 f[1][2]=2       f[5][2]=1

  所以 f[1][3]=min(f[1][2],f[5][2])=1

  初始状态:f[i][0]=d[a[i]]    loc[i][0]=a[i]

  注意这里 f 记录的是它的深度的最小值,而位置用 loc 记录

 void init()
{
int i,j,s,x,k;
for (i=;i<=top;i++)
{
f[i][]=d[a[i]];
loc[i][]=a[i];
}
s=log2(top);
for (j=;j<=s;j++)
{
k=top-(<<j)+;
for (i=;i<=k;i++)
{
x=i+(<<(j-));
if (f[i][j-]<=f[x][j-])
{
f[i][j]=f[i][j-];
loc[i][j]=loc[i][j-];
}
else
{
f[i][j]=f[x][j-];
loc[i][j]=loc[x][j-];
}
}
}
}

  代码用变量优化了一下常数


  接着开始进行询问

  读入两个节点,查询它们第一次出现的位置

  在这两个位置之间的区间查询最小深度的节点,该节点即为最近公共祖先

  查询区间时,我们把它分成两个部分,可以有重叠,如

    8 9 6 5 6 8 4

  这7个节点,把它分成: 8 9 6 5  和 5 6 8 4

              min=5      min=4

  则最小值为 min(5,4)=4

     min(f[x][log2(y-x)],f[y-(<<i)+][log2(y-x)]);

  可以这样理解:

    将两个位置的距离取个对数记为 i,然后从最左边,往后共 2个数的最小值,这是第一部分

    第二个部分是从右边往左推 2个数,即 y-2i+1,然后再往后取 2个数

  成功将区间分为两部分

     scanf("%d",&t);
while (t>)
{
t--;
scanf("%d%d",&x,&y);
x=b[x];
y=b[y];
if (x>y) swap(x,y);
i=log2(y-x);
k=y-(<<i)+;
printf("%d\n",f[x][i]<f[k][i]?loc[x][i]:loc[k][i]);
}

  代码内有常数优化,有的地方思路可能不是很清晰,请谅解

  给个完整代码

 #include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#define N 100001
using namespace std; int a[N*],d[N],down[N],next[N],top,f[*N][],loc[*N][],n,b[N];
int log2(int x)
{
int k=;
while (x>)
{
x/=;
k++;
}
return k;
}
void dfs(int x,int dep)
{
int i;
d[x]=dep;
a[++top]=x;
for (i=down[x];i!=;i=next[i])
{
dfs(i,dep+);
a[++top]=x;
}
}
void init()
{
int i,j,s,x,k;
for (i=;i<=top;i++)
{
f[i][]=d[a[i]];
loc[i][]=a[i];
}
s=log2(top);
for (j=;j<=s;j++)
{
k=top-(<<j)+;
for (i=;i<=k;i++)
{
x=i+(<<(j-));
if (f[i][j-]<=f[x][j-])
{
f[i][j]=f[i][j-];
loc[i][j]=loc[i][j-];
}
else
{
f[i][j]=f[x][j-];
loc[i][j]=loc[x][j-];
}
}
}
}
int main()
{
int i,k,x,y,t;
scanf("%d",&n);
for (i=;i<=n;i++) down[i]=d[i]=next[i]=;
for (i=;i<=n;i++)
{
scanf("%d",&x);
next[i]=down[x];
down[x]=i;
}
top=;
dfs(down[],);
for (i=;i<=top;i++) if (b[a[i]]==) b[a[i]]=i;
init();
scanf("%d",&t);
while (t>)
{
t--;
scanf("%d%d",&x,&y);
x=b[x];
y=b[y];
if (x>y) swap(x,y);
i=log2(y-x);
k=y-(<<i)+;
printf("%d\n",f[x][i]<f[k][i]?loc[x][i]:loc[k][i]);
}
return ;
}

版权所有,转载请联系作者,违者必究

QQ:740929894

求LCA最近公共祖先的在线ST算法_C++的更多相关文章

  1. 求LCA最近公共祖先的离线Tarjan算法_C++

    这个Tarjan算法是求LCA的算法,不是那个强连通图的 它是 离线 算法,时间复杂度是 O(m+n),m 是询问数,n 是节点数 它的优点是比在线算法好写很多 不过有些题目是强制在线的,此类离线算法 ...

  2. 求LCA最近公共祖先的在线倍增算法模板_C++

    倍增求 LCA 是在线的,而且比 ST 好写多了,理解起来比 ST 和 Tarjan 都容易,于是就自行脑补吧,代码写得容易看懂 关键理解 f[i][j] 表示 i 号节点的第 2j 个父亲,也就是往 ...

  3. LCA最近公共祖先(Tarjan离线算法)

    这篇博客对Tarjan算法的原理和过程模拟的很详细. 转载大佬的博客https://www.cnblogs.com/JVxie/p/4854719.html 第二次更新,之前转载的博客虽然胜在详细,但 ...

  4. 【LCA最近公共祖先】在线离线

    [在线] 1.倍增法 现将深度较大的跳至与深度较小的统一深度.预处理$fa[u][i]$表示$u$往上跳$2^i$个单位后的祖先,则就可以像快速幂一样,将移动的步数化为二进制,如果第$i$位为$1$, ...

  5. cogs 2450. 距离 树链剖分求LCA最近公共祖先 快速求树上两点距离 详细讲解 带注释!

    2450. 距离 ★★   输入文件:distance.in   输出文件:distance.out   简单对比时间限制:1 s   内存限制:256 MB [题目描述] 在一个村子里有N个房子,一 ...

  6. LCA(最近公共祖先)之倍增算法

    概述 对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u.v的祖先且x的深度尽可能大. 如图,3和5的最近公共祖先是1,5和2的最近公共祖先是4 在本篇中我们先介 ...

  7. LCA(最近公共祖先)专题(不定期更新)

    Tarjan(离线)算法 思路: 1.任选一个点为根节点,从根节点开始. 2.遍历该点u所有子节点v,并标记这些子节点v已被访问过. 3.若是v还有子节点,返回2,否则下一步. 4.合并v到u上. 5 ...

  8. LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现

    首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点. 换句话说,就是两个点在这棵 ...

  9. 【图论算法】LCA最近公共祖先问题

    LCA模板题https://www.luogu.com.cn/problem/P3379题意理解 对于有根树T的两个结点u.v,最近公共祖先LCA(u,v)表示一个结点x,满足x是u.v的祖先且x的深 ...

随机推荐

  1. PHP处理mysql事务

    MYSQL的事务处理主要有两种方法.1.用begin,rollback,commit来实现begin 开始一个事务rollback 事务回滚commit 事务确认2.直接用set来改变mysql的自动 ...

  2. ./vi: line 2: mkdir: command not found

    当前两天博主在写脚本的时候,运行脚本时候总是出现此消息,很郁闷, 开始我以为可能是我mkdir的函数库依赖的问题,但是当我用其他的脚本创建 目录的时候,命令又可以用了,找了半天,终于找到了答案 --- ...

  3. C语言函数篇(四)函数的设计

    1. 函数设计的时候,如果使用到全局变量,就尽量通过参数的形式传递进来 也就是说,保持 函数 跟 外部的交互 只有 参数 和 返回值 2. 在有参数的情况下,或者有数值输入的时候,要先进行错误判断. ...

  4. 为什么i=i++后,i的值不变(深入解析)

    在Java中,运行以下代码: int i=10; i=i++; System.out.println(i); 得到的结果仍然为10,为什么呢?理论上,运算的过程不应该是i首先把10取出来,赋值给i,然 ...

  5. 记录下MoKee编译过程

    纯属记录帖 关注和了解这个rom有段时间了. 最近有需要了解odex,折腾了几天还是在坑里. 索性,先编译下MoKee看看. 之前make过 4.2 和 5.1 ,刷到模拟器和N5里. 编译教程可以参 ...

  6. mysql之select查询:练习

    单表查询: 数据查询命令:select 识别要查询的列 from识别要查询的表 select 运算符: + .-.*./. 加减乘除 等于= 不等于!= 或 <> 大于等于>= 小于 ...

  7. adb启动和关闭

    启动adb服务: cmd("adb start-server"); 关闭adb服务: cmd("adb start-server");

  8. 抽象类和虚方法、base关键字

    微软官方文档:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/classes-and-structs/abstract ...

  9. Mac 电脑鼠标和触摸板滚动方向不一致的问题【已解决】

    当我们使用鼠标连接到 MacBook 时,会发现无论怎么设置,鼠标和触摸板的滚动方向都是相反的,导致不能同时使用鼠标和触摸板 解决方法: 我安装了下面的程序,它只允许您反转鼠标的滚动行为: Scrol ...

  10. ironic rescue standard rescue and unrescue process

    翻译官网救援/取消救援标准流程 1.用户在节点上调用Nova rescue 2.Nova ComputeManager调用virt驱动程序的rescue()方法,传入rescue_password作为 ...