最形象的讲解,让你一次学会什么叫LCA离线算法tarjan

Tarjan 的 LCA 算法 c++写的Tarjan 的 LCA 算法,最近公共祖先算法,可供算法学习参考 立即下载

今天刚学了LCA离线tarjan算法。下面跟大家分享一下我的学习心得。
首先LCA是Lowest Common Ancestors的缩写。中文名为最近公共祖先。然后就是什么叫离线,与离线相对应的是在线算法(用dfs + rmp)的区别在哪里。
离线算法就是把所有的询问存起来,最后一次解决输出结果。结果有可能需要所有的询问(更新)之后才能得到。
在线算法就是每次询问一次,输出询问结果,每次的结果只与这次的询问有关,与其他询问无关。
我在这里先讲一下离线的tanjan算法,我会陆续的学习在线算法,到时候也会写一篇博客,欢迎大家一起学习,讨论。
tanjan算法是基于并查集实现的,如果还有萌新不知道什么叫并查集,那么请先去学习有关并查集的知识,不然可能会不懂这个tanjan算法。
那么下面我就着重说一下tanjan算法具体是怎样实现的。
首先我们把树上所有的节点的父亲设置成这个节点本身。我们用father[i]表示节点i的父亲,所以这个用代码实现就是

for(int i = 1; i <= n; i++)
{
   father[i] = i;
}

做完之后首先找到整棵树的根节点root,我们从跟节点开始dfs.
dfs的过程是
1.首先判断有没有询问与这个点有关,比如当前dfs到了u这个点,有一个询问是与u有关的,比如其中的一个询问是询问u与点v的最近公共祖先。
那么我们先判断一下v是否已经被访问过,如果被访问过,则u,v的最近公共祖先就是lca = Find(v);其中Find函数实现如下:

int Find(int x)
{
  if(x == father[x]) return father[x];
  else return father[x] = Find(father[x]);
}

如果v没有被访问则直接执行以下步骤:
2.visit[u] = true;
3.dfs u的每一个儿子节点,比如u有一个儿子son,那么先dfs(son),dfs完了之后把son的父亲置为u。(刚开始son的父亲是自己,这个在初始化时已经做完了)
上面所以的实现可以用以下代码表示,其中LCA[i][j] 表示i与j的最近公共祖先。ask[i][j]表示要询问i与j的最近公共祖先,g[i]表示i的儿子向量。

void dfs(int u)
{
   for(int i = 1; i <= n; i++)
   {
      if(visit[i]&&ask[u][i])
      {
        LCA[u][i] = Find(i);
      }
   }
   visit[u] = true;
   for(int i = 0; i < g[u].size(); i++)
   {
     int son = g[u][i];
     dfs(son);
     father[son] = u;
   }
}

可能看了上面的文字讲解和代码实现,你还是有一种似懂非懂的感觉,可能会产生这样一个疑问,为什么这样做是对的?为什么这样做就能求出两个点的最近公共祖先?产生了这样的疑问是好事,说明你在思考,而不是强行记模板。下面我用两张图来简单的说一下为什么这样做就能求出两个点最近公共祖先。
对于任意两个点(u,v),只能有两种情况1.v是u的一个儿子,或者说v在以u为根节点的子树中(或者反过来u是v的儿子,都是一个道理,我们这里为了方便,就假设v是u的儿子(可能不是直接儿子))
2.u,v没有儿子关系

对于第一种情况假如如下图:
这里写图片描述
根据dfs的性质可知,首先dfs到点u,这时发现有一个询问是关于u,v的,但是此时v还没有被访问到,所以在这里先不做处理,继续往下dfs,注意此时虽然u点已经被访问到了,但是father[u]还是u,因为以u为根的整棵子树还没有访问完。然后当dfs到了v时,发现有一个询问是关于v,u的然后判断u有没有被访问,很显然此时u早已经被访问所以LCA[v][u] = Find(u) = u;
对于第二中情况假如如下图:
这里写图片描述
根据dfs的性质,有可能先访问到u,也有可能先访问到v。所以我们可以分为两种情况讨论。
1.先访问到v
当访问到v时,因为u还没有被访问,所以不做处理,当访问完v之后father[v] = x,如图,然后father[x] = f,然后访问到u,这时发现v已经被访问过了,所以LCA(u,v) = Find(v) = Find(x) = Find(f) = f.

2.先访问到u.
当访问到u时,因为v还没有被访问,所以不做处理,当访问完以u为根的子树后,father[u] = f;然后访问x,然后访问v.当访问到v时,发现u已经被访问了,所以LCA(v,u) = Find(u) = f;

至此,上面两种情况已经讨论完了,你现在应该已经明白了为什么这样做是对的的原因。

那么理论知识我们学习完了,我们下面去实践一下,我们以poj1470为例。
ac代码如下:

#include<stdio.h>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 1000;
int ask[maxn][maxn];//保存询问
int ans[maxn];//保存祖先i出现过的次数
int n,m;
vector<int> g[maxn];//保存儿子
int root;//树的根
bool visit[maxn];
bool isroot[maxn];
int father[maxn];
int Find(int x)
{
    if(father[x] == x) return x;
    else return father[x] = Find(father[x]);
}
void init()
{
    memset(ans,0,sizeof(ans));
    memset(visit,false,sizeof(visit));
    memset(isroot,true,sizeof(isroot));
    memset(ask,0,sizeof(ask));
    for(int i = 1; i <= n; i++)
    {
        g[i].clear();
        father[i] = i;
    }

}
void LCA(int root)
{
    for(int i = 1; i <= n; i++)
    {
        if(visit[i]&&ask[root][i])
        {
            ans[Find(i)] += ask[root][i];
        }
    }
    visit[root] = true;
    for(int i = 0; i < g[root].size(); i++)
    {
        int term = g[root][i];
        LCA(term);
        father[term] = root;
    }
}
int main()
{
    while(~scanf("%d",&n))
    {
        init();
        int f,s,num;
        for(int i = 1; i <= n; i++)
        {
            scanf("%d:(%d)",&f,&num);
            for(int j = 1; j <= num; j++)
            {
                scanf(" %d",&s);
                isroot[s] = false;
                g[f].push_back(s);
            }
        }
        for(int i = 1; i <= n; i++)
        {
            if(isroot[i])
            {
                root = i;
                break;
            }
        }
        scanf("%d",&m);
        int u,v;
        for(int i = 1; i <= m; i++)
        {
            scanf(" (%d %d)",&u,&v);
            ask[u][v]++;
            ask[v][u]++;
        }
        LCA(root);
        for(int i = 1; i <= n; i++)
        {
            if(ans[i])
            {
                printf("%d:%d\n",i,ans[i]);
            }
        }
    }
    return 0;
}

以上就是我今天学了一上午的LCA离线tarjan算法的心得,希望对大家有用,如果上面有什么不对的地方,欢迎各位大佬指出.

车载导航测试,面试常见问题 车载导航测试是汽车软件测试的重要组成部分,涉及验证导航系统的功能、性能、可靠性和用户体验。在面试中,面试官通常会考察候选人的测试思维、问题解决能力和实际经验。以下我将从常见问题类别入手,逐步介绍面试中的高频问题,并提供结构化分析。通过以上结构化准备,你能在面试中自信应对车载导航测试相关问题。用户体验测试关注界面友好性和交互流畅性,安全测试则涉及功能安全和数据隐私。功能测试聚焦于导航核心功能的正确性,如路线规划、位置定位和地图显示。面试官会考察你如何识别、报告和跟踪缺陷,以及熟悉测试工具。 阅读详情

相关推荐

不用敲命令!用宝塔面板可视化部署pbootCMS的保姆级教程(2024最新版)

本文提供2024年最新版pbootCMS可视化部署教程,全程使用宝塔面板操作,无需任何命令行知识。从服务器初始化、LNMP环境配置到源码上传、安装向导,手把手指导用户通过图形化界面快速搭建专业网站,适合零基础新手和追求高效建站的用户。

weixin_42509507的博客 490

在线算法和离线算法的概念(转)

一、在线算法   在计算机科学中,一个在线算法是指它可以以序列化的方式一个个的处理输入,也就是说在开始时并不需要已经知道所有的输入。相对的,对于一个离线算法,在开始时就需要知道问题的所有输入数据,而且在解决一个问题后就要立即输出结果。例如,选择排序在排序前就需要知道所有待排序元素,然而插入排序就不必。   因为在线算法并不知道整个的输入,所以它被迫做出的选择最后可能会被证明不是最优的,对在线算...

lang_a的博客 8340

树莓派5搭建ROS1开发环境:从系统安装到环境变量配置

本文详细指导如何在树莓派5上搭建ROS1开发环境。针对树莓派OS非标准安装路径导致的常见CMake报错问题,提供了从系统烧录、一键安装ROS到关键环境变量配置的完整解决方案,确保工作空间能成功编译,助力机器人项目开发。

weixin_29159127的博客 385

【算法学习03】Tarjan&LCA

在有根树中,找出某两个结点u和v最近的公共祖先。一般有两种方法:树上倍增大跳求LCA和Tarjan离线算法求LCA,本篇主要描述Tarjan离线算法求LCA。

m0_46121643的博客 1345

阿里云百炼接入本地Claude,接入idea

阿里云百炼平台提供两种接入方式:未购买套餐时使用通用API Key(sk-xxxxx),需注意免费额度独立计算且不会自动切换模型;购买Coding Plan套餐后使用专属API Key(sk-sp-xxxxx),享受固定月费无超支风险。配置需在.claude/settings.json中设置认证信息,包括API Key和Base URL。建议开启"用完即停"功能避免意外扣费,不同模型的免费额度互不影响。购买套餐后还可通过IDEA插件Claude Code进行集成,实现固定费用下的稳定使用

fengtangjiang的博客 1360

PyFluent进阶实战:自动化密闭几何(Watertight)网格生成

本文深入探讨了如何利用PyFluent实现自动化密闭几何(Watertight)网格生成。通过Python脚本控制Fluent Meshing工作流,工程师可以高效、一致地完成从几何导入、局部尺寸定义、面网格生成到边界层设置和体网格输出的全过程,从而将复杂的CFD前处理工作转化为可重复、可集成的自动化流程,极大提升仿真效率和知识复用性。

t8u9v0w1x的博客 578

详解使用 Tarjan 求 LCA 问题(图解)

LCA问题有多种求法,例如倍增,Tarjan。 本篇博文讲解如何使用Tarjan求LCA。 如果你还不知道什么是LCA,没关系,本文会详细解释。 在本文中,因为我懒为方便理解,使用二叉树进行示范。 LCA是什么,能吃吗? LCA是树上最近公共祖先问题。 最近公共祖先就是树上有两个结点,找一个结点,是他们的公共祖先,并且离他们两个结点最近。 例如这是一棵树: 树上 4,7 两个...

weixin_34315485的博客 1395

claude使用教程

提供了三种模型:claude,codex, gemini,三种模型都共用同一个apikey, 有开发需求使用claude效果较好,知识问答等场景gemini效果会更好些,本文只提供了claude的安装方式,剩下两种按需按照以下参考链接安装下即可,安装有问题参考文档:https://www.aicodemirror.com/dashboard/official-installation/macos-linux (不需要挂梯子,自己手机号注册就好)

lichao19871216的博客 3497

Tarjan 算法解决 LCA 问题

算法

实践求真知 979

华为2021年天线工程师笔试题目回忆总结(持续更新)

1. 判定长线和短线 所谓长线是指传输线的几何长度和线上传输电磁波的波长的比值(即电长度)大于或接近于1。反之称为短线。是相对于波长而言的。 1000m的传输线,对于频率为50hz(即波长为6000km)的交流电来说,仍远小于波长,应视为短线。长线和短线的区别还在于:长线为分布参数电路,而短线是集总参数电路。 2. 判定传输线的容性和感性 传输线上的输入阻抗具有以下特点: 1/4波长变换性:感性负载与容性负载变换,开路与短路变换; 1/2波长重复性:相距1/2波长的整数倍的两个点输入阻抗都相等。1/4开路传

ludashi3的博客 9530

ANSYS Fluent中的声学模型:实用指南

文章摘要 本文介绍了ANSYS Fluent中三种气动声学模拟方法:Ffowcs-Williams & Hawkings(FWH)模型、宽带噪声源模型和波动方程模型。FWH模型适用于外部气动声学问题,需要瞬态流场数据来预测远场噪声;宽带模型基于稳态RANS模拟,可快速识别噪声源位置,适合早期设计阶段;波动方程模型直接求解声波方程,提供全域声压分布,但仅适用于低马赫数流动。文章详细说明了各模型的使用场景、设置步骤和关键参数配置,为工程应用提供了实用指南。

weixin_45793544的博客 1323

trajan算法求lca 超级详细配图讲解

关于离线算法 (下面内容可以略过。) 离线算法其实就是将多个询问一次性解决。离线算法往往是与在线算法相对的。例如求LCA的算法中,树上倍增属于在线算法,在对树进行O(n)O(n)O(n)预处理后,每个询问用O(log2n)O(log_2n)O(log2​n)复杂度回答。而离线的Tarjan算法则是用O(n+q)O(n+q)O(n+q)时间将询问一次性全部回答。 详解 下面是一棵树,我们将以这棵树为例子讲解Tarjan算法,其中0号点为根。 假设对于这棵树的询问有4个,分别询问: LCA(2,8)LCA(2

m0_50219534的博客 1261

Fabric V2.5 通用溯源系统——使用Hyperledger Caliper压力测试

Caliper是一款测试区块链性能的工具,支持Hyperledger Fabric、Ethereum等,支持丰富的测试配置参数,测试完成后将直接输出报告,相较于tape更加专业,但是配置稍微有些复杂。本文将一步一步配置Caliper以测试Fabric V2.5 通用溯源系统中的注册用户、查询用户、农产品上链、农产品查询的区块链性能。

TrueTechLabs 官方博客 1396

LCA

作为一个数据结构学傻,终于要开始入这个坑了…… 之前一直不写有关数据结构的东西,是因为不会画那种很高端的图,所以一直不写,现在看来似乎没有办法避免了…… LCA,全称Least Common Ancestor,即最近公共祖先,顾名思义,LCA(u,v)就是u和v的所有共同祖先中深度最小的,很多算法对于树上一根链的处理通常都需要围绕LCA展开,因此快速求得树上两个节点的LCA是树上算法的基...

as7223979的博客 286

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

关于LCA的三种解法

百度百科关于LCA的解释:LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。(有多种变型例如求两点间的距离如HDU2586,求最大公共的长度如CodeForces - 832D等等) 题目:POJ 1984 HDU 2586ZOJ 3195POJ 1330CodeForces - 832D 1.跳跃...

weixin_30649859的博客 126

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Tarjan求LCA(最近公共祖先)

在有根树中,找出某两个结点u和v最近的公共祖先。一般有两种方法:树上倍增大跳求LCA和Tarjan离线算法求LCA,本篇主要描述Tarjan离线算法求LCA。5、寻找与当前节点u有关的查询,判断所查询的除了u以外的另一节点u’是否已访问。6、若u’已经被访问过,则u和u’的LCA就是u’已经被合并到的节点。2、遍历当前节点u的所有子节点(邻居节点)v,并置u节点为已访问。1、任选一个节点root作为根节点,从根节点开始遍历。3、若v还有子节点,则继续递归其子节点。4、并查集合并v到u上面。

2302_81590667的博客 740

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

c++基础树上问题——求lca(最近公共祖先)的几种方法,算法必看哟!

详细解释c++数据结构树中的求lca最近共工作祖先的方法以及相关例题详解,适合参加算法比赛和对算法感兴趣的人!

qizhiburce的博客 1626

Tarjan求LCA

Tarjan求LCA是一种离线的LCA求法,它需要先知道所有的询问,之后通过dfs和并查集维护以求出LCA,这是一种线性的做法,如果有n个节点m次询问,那么复杂度为O(n+m)。 具体做法怎么做呢?其实就是dfs+并查集。 我们首先用链式前向星去存这棵树,存所有的询问关系(为什么一会再说),注意询问关系存成无向图。每个节点的父节点一开始设置为自己。然后我们从根结点开始dfs,优先去dfs自己的...

weixin_30325793的博客 263

Tarjan 求解 LCA

本文介绍了使用Tarjan算法离线求解最近公共祖先(LCA)的方法。该算法通过DFS遍历树结构,并利用并查集维护已访问节点的祖先信息。在回溯过程中合并子节点到父节点的集合,当处理查询时,若两点均已访问,则通过并查集快速找到其LCA。算法时间复杂度接近线性(O(nα(n)+Q)),其中α(n)为反阿克曼函数。文中提供了完整的C++实现代码模板,包括邻接表建树、查询处理和异或和计算等核心功能,适用于处理树结构的路径查询问题。该算法高效且易于实现,是解决LCA问题的经典方案。

Bree0307的博客 502

LCA算法-Tarjan算法

成为tarjan需要多少根头发(bushi

EQUINOX1的博客 1733

利用Tarjan算法(LCA)求树上任意两点的距离

AcWing 1171 利用Tarjan算法求树上任意两点的距离 Tarjin算法求最近公共祖先(LCA)

u014285914的博客 4856

c++算法之高级数据结构篇 - Tarjan算法求LCA

Tarjan算法是一种高效的离线LCA(最近公共祖先)求解算法,巧妙结合DFS与并查集。其核心思路是在DFS过程中标记已访问节点,并通过并查集维护访问路径:当访问节点u时,若查询对象v已被访问,则LCA(u,v)即为v所在集合的代表节点;回溯时将子节点集合合并到父节点。算法时间复杂度接近O(n+Qα(n)),远优于在线算法,但需提前收集所有查询。典型实现使用邻接表存储查询,配合路径压缩优化的并查集,在DFS递归中完成查询应答。该算法体现了"整体处理"的离线思维优势,适合批量查询场景。

巴巴博一巴巴博一巴巴博一 1008

【模版】Tarjan离线算法求最近公共祖先(LCA)

【模版】Tarjan离线算法求最近公共祖先(LCA) 储备知识 离线算法 离线算法其实就是将多个询问一次性解决. 深度优先生成树 并查集查询、合并最大祖先 最近公共祖先(Least Common Ancestors) 两个点在一棵无环棵树上距离最近的公共祖先节点. LCA还可以将自己视为祖先节点. 链式前向星 什么是前向星:前向星是一种特殊的边集数组 我们建立边结构体为: struct Edge{...

恒治学 521

LCA之Tarjan离线算法

第一个for循环是遍历该节点的儿子,遍历完之后再把这个节点的并查集映射设为他的爸爸,之后遍历他的查询,如果他查询的另一个对象已经查询过了,那么就记录到ans数组里,存为答案。

m0_74977202的博客 1108
上一篇: codeforces/contest/798/problem/B
下一篇: Seven Segment Display(数位dp)
creatorx
博客等级 码龄10年 127粉丝 145原创
评论 10
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值