最形象的讲解,让你一次学会什么叫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

GIS分析-利用K-Means方法进行地震分析

通过“聚类”可以获得有关我们所拥有的数据的各种线索,这是数据挖掘技术之一。分析创建的组有助于决策过程。在这项研究中,我们将使用数据挖掘工具之一的K-Means方法研究安纳托利亚东部地区近100年发生的地震的空间分布,并试图获得有用的信息,以供未来的研究使用。我从局地震目录页面获得了研究所需的地震数据,并从获得了区域边界数据。

gis收藏家的博客 1570

浅谈 Tarjan 算法

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

William_Wang_的博客 5220

验证(uvm常见问题)--子类和父类的句柄和对象问题

sv中的子类和父类 在sv中,有如下两个类,child继承于parent。然后在test module中,正确转换的方法如下面所示。其中c1,c2,p1 都指向了同一个子类的对象c1。总结如下: 一个子类的对象是包括子类的成员变量,方法和父类的成员变量,方法。 子类的句柄c1可以直接赋值给父类的句柄p1,此时p1只可以访问父类的成员变量和方法,或者子类的虚方法,比如下面调用p1.display是...

q876507447的博客 7633

变色龙哈希函数 Chameleon Hash 可变型区块链

哈希函数 Hash: 众所周知,区块链有着极其优秀的安全性就是因为其充分使用了哈希函数。 哈希简单用一句话来讲,就是:将任意长度输入的字串可转换成一个固定长度的字串,通过原始字串可以很容易地算出转换后的字串,通过转换后的字串很难还原出原始字串。 哈希函数特征: 1. 对于任意m作为输入,得到输出的结果,很难找到另一个输入m'  (m'不等于m),使得m'的Hash结果也为同样的输出

新手村 1万+

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

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

qaqwqaqwq的博客 1万+

无网环境也能玩转DBeaver?手把手教你离线安装与ClickHouse连接(附实测jar包)

本文详细介绍了在无网环境下如何离线安装DBeaver并配置ClickHouse连接的完整指南。通过准备必备工具如DBeaver安装包、ClickHouse JDBC驱动及Maven本地仓库,确保在物理隔离环境中实现数据库管理。文章包含详细的安装步骤、驱动配置和性能优化建议,帮助开发者在严格网络限制下高效工作。

weixin_42635064的博客 375

Tarjan 算法学习笔记

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

m0_72410588的博客 1699

DeepSeek的训练与优化流程

DeepSeek的训练体系不仅追求极致的计算效率,更注重将物理规律、人类知识、安全伦理深度融入训练过程,其"效率-安全-可解释性"三角平衡的设计理念,为行业提供了重要参考范式。

m0_56896669的博客 1437

海思3559av100平台lan9500模块调试记录

1. 前言 通过USB2.0接口扩展一路百兆虚拟网口 2. 配置lan9500驱动 make ARCH=arm64 menuconfig 开启LAN95XX驱动,编译进内核 保存内核配置 3. 编译内核 make ARCH=arm64 CROSS_COMPILE=aarch64-himix100-linux- uImage -j 20 cd ../../arm-trusted-firmware/arm-trusted-firmware ./mk.sh cp ...

jzwjzw19900922的博客 909

LCA

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

as7223979的博客 286

Central Finance 全景解析:用 SAP S/4HANA 构建跨系统的财务中台与渐进式转型路径

Central Finance 是基于 SAP S/4HANA 的一种部署与应用场景:在不打断现有异构源系统运营的前提下,把多套 SAP 与非 SAP 系统中的财务与管理会计数据实时/准实时复制到一套中心化的 SAP S/4HANA 中,作为统一的记账与分析底座,逐步把选定的财务流程(如预算控制、付款、固定资产结账等)迁入中心系统。这让企业既能快速获得统一报表与统一核算结构,又为未来的整体迁移留出了弹性路径。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 392

关于LCA的三种解法

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

weixin_30649859的博客 126

Altium Designer应用技巧13: 四层板的拼版方法

本文介绍在Altium Designer中,进行4层板的拼版方法。Altium Designer有多种拼版方法,本文选择最直观的复制、删帖方法来完成,步骤如下: 1、看一下原始文件,如下图所示,我们要对该板进行拼版操作。 2、通过菜单“报告”-“板子信息”得到板子大小,在这里一般切换为mm比较好操作,我们可以使用快捷键"q"。 3、如果是水平拼版,我们需要关心水平长度;如果是垂直拼版...

snmplink的博客 1万+

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

强连通分量-tarjan算法缩点

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

c__chong的博客 3501

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

Tarjan 算法(超详细!!)

之前太多错误了,重构了

WerChange的OI博客 7288

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

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、付费专栏及课程。

余额充值