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

Tarjan算法详解(AcWing 1174 受欢迎的牛) 题目描述: 每一头牛的愿望就是变成一头最受欢迎的牛。 现在有 N 头牛,编号从 1 到 N,给你 M 对整数 (A,B),表示牛 A 认为牛 B 受欢迎。 这种关系是具有传递性的,如果 A 认为 B 受欢迎,B 认为 C 受欢迎,那么牛 A 也认为牛 C 受欢迎。 你的任务是求出有多少头牛被除自己之外的所有牛认为是受欢迎的。 输入格式 第一行两个数 N,M; 接下来 M 行,每行两个数 A,B,意思是 A 认为 B 是受欢迎的(给出的信息有可能重复,即有可能出现多个 A,B)。 输出格式 输出被除自己之外的 阅读详情

今天刚学了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算法的心得,希望对大家有用,如果上面有什么不对的地方,欢迎各位大佬指出.

Tarjan算法详解 目录一、什么是强连通分量1. 概念2. 强连通分量二、两种dfs遍历1. 方式12. 方式2三、一个简单例子理解算法四、更完整的一个例子五、Code实现 一、什么是强连通分量 tarjan强连通分量算法 1. 概念 连通:无向图中,从任意点i可到达任一点j 强连通:有向图中,从任意点i可到达任一点j 弱连通:把有向图看作无向图时,从任意点i可到达任一点j 如图,强连通无论那个点,都能按照方向到达任意一点,弱连通如果强行按方向,那么B到不了C,A到不了B和C,C到不了B。但如果把他看作是无向图,那么他们也 阅读详情

相关推荐

算法学习笔记(Tarjan)

本文介绍Tarjan求强联通分量、找割点和割边、找环。

qq_74405082的博客 2180

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

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

lang_a的博客 8340

高项49个过程,输入、工具技术、输出的思维导图

为了制定项目章程的管理方法主要是采用了数据收集技术,例如通过头脑风暴,通过召集主题专家和团队成员收集信息、解决方案,并在短时间内获得大量创意,并对大量创意进行分析和排序,选出最优化最适合本项目方案,并对项目成员的不同差异观点,进行了引导和对冲突的管理,最终有助于项目干系人的目标、成功标准的内容达成一致意见,形成完善的项目章程。在本过程中得到项目章程,在制定项目章程中需要包括信息有:项目名称、项目目标、项目总里程碑进度表、项目经理、主要干系人及财务预算信息。本过程在仅开展一次,或仅在项目的预定义点开展。

Legend_Jiang的博客 6182

浅谈 Tarjan 算法

无向图中,若删去一条边会使得这个图的极大连通分量数增加,则该边被称为桥。也可以理解为无向图的一个连通块中,若删除一条边会使得至少两点之间无法相互到达,该边被称为桥。无向图中,若删去一个点会使得这个图的极大连通分量数增加,这个点被称为割点。也可以理解为无向图的一个连通块中,若删除一个点会使得至少两点之间无法相互到达,该点被称为割点。注意,强连通分量(SCC,Strongly Connected Components)是在单向图中的。强联通子图,定义为:在单向图中,该子图上的任意两点之间能互相到达。

William_Wang_的博客 5220

汽车TARA分析实战:从威胁建模到安全需求落地的完整指南

在汽车电子电气系统开发中,威胁分析与风险评估(TARA)是确保功能安全与网络安全的核心方法论。其原理在于通过结构化的流程,系统性地识别资产、分析威胁场景并评估风险,旨在将安全理念前置,从设计源头规避风险。这一过程的技术价值在于将模糊的安全担忧转化为可量化、可追溯的工程需求,从而指导资源精准投放,是符合ISO 26262和ISO/SAE 21434标准要求的关键实践。其应用场景贯穿智能汽车从概念设计到系统开发的各个阶段,尤其对于涉及自动驾驶、智能座舱、车云通信等复杂功能的域控制器开发至关重要。本文聚焦于TAR

weixin_42706667的博客 305

Frida动态Hook实战:逆向美团外卖mtgsig3.0签名算法

在移动应用安全与数据爬取领域,API签名是保障请求合法性与数据完整性的核心机制。其原理是通过特定算法,将请求参数、时间戳等动态要素加密生成唯一字符串,服务器端通过验签防止数据篡改与伪造。这项技术的核心价值在于,它构建了客户端与服务器之间的可信通道,是移动端数据交互安全的基础。在自动化测试、安全审计、市场数据分析等应用场景中,理解并能够获取或绕过签名验证是关键前提。本文聚焦于美团外卖App的核心签名参数mtgsig3.0,针对其复杂的算法逻辑,提供了一套基于Frida的动态Hook解决方案。通过Hook技术,

weixin_33935636的博客 834

Tarjan算法超超超详解(ACM/OI)(强连通分量/缩点)(图论)(C++)

深入浅出的Tarjan算法手把手讲解!看完就会!

qaqwqaqwq的博客 1万+

libjpeg-turbo图片解码 VS opencv

本文对比了libjpeg-turbo和opencv在图像解码层面的耗时情况,及解码数据是否存在diff

优快云_WHB的博客 834

Tarjan 算法学习笔记

支持度(Support)是指数据集DDD中包含某个项集XXX的记录所占的比例。SuppXfreqX∣D∣SuppX∣D∣freqX​其中freqXfreq(X)freqX表示包含XXX的记录数,∣D∣|D|∣D∣表示数据集DDD中记录的总数。置信度(Confidence)是指数据集DDD中包含项集XXX的记录中同时包含另一个项集YYY的比例。

m0_72410588的博客 1699

APS生产排程系统——ERP、MES、APS系统的关联和区别

APS生产排程系统——ERP、MES、APS系统的关联和区别

这里有一只小马吖 2万+

ArtMate —— 美术公开课智能体开发全记录

本文档详细描述了一款专为中小学美术公开课设计的智能教学助手的开发过程。该智能体融合了多模态感知、自然语言交互、风格迁移与智能评价等技术,旨在辅助教师开展生动、高效的美术鉴赏与创作课程。全文包含需求分析、系统架构设计、核心模块代码实现、交互界面搭建及完整运行示例,并配有详尽的行内注释和设计思想阐述,为教育技术开发者提供一份可落地、可扩展的参考方案。

huanghm88的专栏 153

LCA

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

as7223979的博客 286

【水文学法总结】河道内生态流量计算方法(含MATLAB实现代码)

简介水文学法实现河道内生态流量计算

WW、forever的博客 5020

关于LCA的三种解法

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

weixin_30649859的博客 126

KylinOS 离线安装 MinIO【图文步骤】

在国产操作系统 KylinOS 中安装 MinIO,详细步骤...

甘蓝的专栏 1536

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

强连通分量-tarjan算法缩点

强连通分量:在有向图G中,如果两个顶点u,v间(u->v)有一条从u到v的有向路径,同时还有一条从v到u的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量。简单点说就是:如果一个有向图中,存在一条回路,所有的结点至少被经过一次,这样的图为强连通图。在强连图图的基础上加入一些点和路径,使得当前的图不在强连通,称原来的强连通的部分为强连通分量。

c__chong的博客 3501

Devexpress16.2.6破解工具

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

Tarjan 算法(超详细!!)

之前太多错误了,重构了

WerChange的OI博客 7288

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

Tarjan算法(详解 + 刷题)

包含 4 道 Tarjan 图论题目和AC代码(讲解)

IT OR 考公? 3344

tarjan算法(相关概念、Tarjan求最大强连通分量、割点、求桥、缩点)

无向图 G 的最大连通子图称为 G 的连通分量。注:这里的最大连通子图的含义为:此图为 G 的连通子图,将 G 的任意一个点加到盖子图中之后,此子图将不再连通。比如这一张图中,很显然,我用三种颜色圈起来的部分都是原图的连通分量。(其实可以十分形象的理解成每一个连通分量都是一个连通块)回想到以前的“亲戚”一题,就是让我们去求在这个无向图中,询问的两个点是不是在同一个连通分量中。值得注意的一点,一个连通图的连通分量就是他本身。

Jiguoyuanyic的博客 1905

tarjan—算法的神(一)

本篇包含 tarjan 求强连通分量、边双连通分量、割点 部分, tarjan 求点双连通分量、桥(割边)在下一篇。 伟大的 Robert Tarjan 创造了众多被人们所熟知的算法及数据结构,最著名的如:(本文的)连通性相关的 tarjan 算法,Splay-Tree,Toptree,tarjan 求 lca 等等。 注:有向图的强连通分量、无向图的双连通分量、tarjan 求最近公共祖先...

weixin_40393403的博客 926

tarjan 算法(Tarjan‘s strongly connected components algorithm)

我们可以想象有向图中边一直往前冲,突然有一个边是往回走的,这样就会连接到刚才已经遍历过的点(可能形成一个环),但是这种边我们需要判断是否连接到了另一个SCC,所以我们需要记录点是否在栈内,如果在栈内说明两个点属于同一个SCC,所以可以更新,如果不在,就不能更新当前点的。当 DFS 遍历过程中发现一个节点的所有后继节点已经搜索完毕,并且该节点是当前 DFS 搜索树中的根节点时,可以将该节点以及其所有后继节点弹出栈,并将它们标记为一个强连通分量。同时我们需要知道该点是否在栈内,定义一个记录数组。

qq_44823758的博客 1096

图的连通性问题之tarjan算法

Tarjan算法一种由Robert Tarjan提出的求解有向图强连通分量的线性时间的算法。 Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。 定义dfn(u)为节点u搜索的次序编号,low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。 当dfn(u)

cax1165 2811

关于tarjan的浅解

这个tarjan主要是为了解决一些关于图的问题。

qq_64468032的博客 542

Tarjan算法缩点

在DFS的过程中,当一个节点的所有邻接节点都被访问完毕后,该节点才会被标记为一个强联通分量的结束点。这是因为Tarjan算法在DFS遍历过程中,最后一个被标记的强联通分量在缩点图中没有入边,从而在拓扑排序中应该是第一个节点。由于Tarjan算法是深度优先的,最后一个被标记的强联通分量在缩点图中没有入边,这意味着它在拓扑排序中应该是第一个节点。在有向无环图中,由于没有环的存在,我们可以利用拓扑排序来确定节点的处理顺序,然后使用动态规划来计算最短路径。你只需要求出这个权值和。共一行,最大的点权之和。

qq_73702550的博客 2245

Tarjan 算法学习笔记(超详细)

对于一张有向图\(S\)的子图\(V\),满足\(V\)内的点两两之间可达,并且不存在一个子图\(G\)使得,通俗的讲就是无法继续扩张的两两可达的子图。对于无向图(注意:图不一定连通),割边的定义为:去掉这条边后图的连通块数量增加。对于无向图(注意:图不一定连通),割点的定义为:去掉这个点后图的连通块数量增加。

weixin_41496326的博客 170
上一篇: 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、付费专栏及课程。

余额充值