显然81篇题解是有点多了,不让我提交。

更为不好的是没有一篇详细的\(tarjan\)(不过我也不会写详细的)。

不过\(tarjan\)并没有我们想象的那样难理解,时间也并不爆炸(巧妙的跳过难写二字)。


好了,下面说一说吧:

\(LCA\)是什么该都知道吧(都翻到我博客了qwq)

度娘这样认为:对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。

直观的图:



呵呵,活跃下气氛。




图中3,5节点的\(LCA\)为2号节点,2,4节点的\(LCA\)为1号节点。

前言:

这题数据贼水的,就算在他还是蓝题的时候(我知道数据没变),依题意暴力即可,开不开\(O2\)都是90分,\(#2\)死活不过啊,交了三四十次的样子。

如果想看代码,就点我

\(tarjan\)求节点\(LCA\)的算法与求割点、强连通分量的算法不太一样,关键是缺少\(num[](\text{很多大佬叫他dfn[]}),low[]\)。 但本质都是深搜,离线(算是吧),而且复杂度小得惊人。


关键是将搜过的节点合并到他的父亲节点上,很容易想到可以用并查集维护,时间复杂度极小。显然此时两节点的\(LCA\)就是最早合并的询问节点的父亲节点,这又是为什么呢?

你想啊,我们在搜索的时候,假设搜到了\(LCA\)节点我们会向一边搜索,然后回溯,搜索另一边,这时一定会到达另一询问点,由于并查集是回溯实现的,这时的父亲节点必是这两点的\(LCA\)。

当然我不会问你懂不懂,因为让我读这个大概或许读不懂,这是在模拟之后得出的经验,还有打表!

点我看大佬讲解(模拟)

放上面那个东西是因为太难写了!!

下面放下代码:

\(Code\):


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
struct edge
{
int to,nxt;
}e[500005<<1];
int head[500005],cnte=0;
void add_edge(int u,int v)
{
e[++cnte].to=v;
e[cnte].nxt=head[u];
head[u]=cnte;
}
struct node
{
int to,num,nxt;
}q[500005<<1];
int qu[500005],cntq=0;
void add_query(int u,int v,int k)
{
q[++cntq].to=v;
q[cntq].nxt=qu[u];
q[cntq].num=k;
qu[u]=cntq;
}
int f[500005];
void init(int n)
{
for(int i=1;i<=n;i++) f[i]=i;
return;
}
int getf(int u)
{
if(!(f[u]^u)) return f[u];
else return f[u]=getf(f[u]);
}
void merge(int u,int v)
{
int t1=getf(u),t2=getf(v);
if(t1^t2) f[t2]=t1;
return;
}
int vis[500005]={0},ans[500005]={0};
void dfs(int cur,int father)
{
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j^father&&!vis[j])
{
dfs(j,cur);
vis[j]=1;
merge(cur,j);
}
}
for(int i=qu[cur];i;i=q[i].nxt)
{
int j=q[i].to;
if(vis[j]) ans[q[i].num]=getf(j);
}
return;
}
int n,m,root;
int l,r;
int main()
{
scanf("%d%d%d",&n,&m,&root);
init(n);
for(int i=1;i<n;i++)
{
scanf("%d%d",&l,&r);
add_edge(l,r);
add_edge(r,l);
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&l,&r);
add_query(l,r,i);
add_query(r,l,i);
}
memset(ans,0,sizeof(ans));
dfs(root,root);
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}

我知道你尝试看懂,但失败了(还是我都把你看成像我这样的蒟蒻了)


下面按分块的思想一一解决:

这里先给出有关变量、数组含义(大工程qwq):

struct edge
{
int to,nxt;
}e[500005<<1];
int head[500005],cnte=0;

这是前向星(邻接表结构体版)

struct node
{
int to,num,nxt;
}q[500005<<1];
int qu[500005],cntq=0;

用于存储询问。

对了,上面这俩要开二倍空间的(都是(类)无向图)

\(vis[]\):代表次点有没有被搜过,防止爆栈(其实是防T)

\(ans[]\):记录每个询问相对的答案


另外,出现了\(6\)个大大小小的函数:

void add_edge(int u,int v)
{
e[++cnte].to=v;
e[cnte].nxt=head[u];
head[u]=cnte;
}

前向星加边,假装都会吧。

void add_query(int u,int v,int k)
{
q[++cntq].to=v;
q[cntq].nxt=qu[u];
q[cntq].num=k;
qu[u]=cntq;
}

类似前向星的数据结构,将询问存入,支持按每个点相关遍历,将查找询问的\(O(q^2)\)优化成了\(O(q)\),仅仅将\(O(2q)\)的空间变成了\(O(7q)\),值了!

void init(int n)
{
for(int i=1;i<=n;i++) f[i]=i;
return;
}
int getf(int u)
{
if(!(f[u]^u)) return f[u];
else return f[u]=getf(f[u]);
}
void merge(int u,int v)
{
int t1=getf(u),t2=getf(v);
if(t1^t2) f[t2]=t1;
return;
}

并查集必备吧,假装都会了?

放篇不错的题解吧

void dfs(int cur,int father)
{
for(int i=head[cur];i;i=e[i].nxt)
{
int j=e[i].to;
if(j^father&&!vis[j])
{
dfs(j,cur);
vis[j]=1;
merge(cur,j);//注意这里是吧j合并到cur上,写反会WA
}
}
for(int i=qu[cur];i;i=q[i].nxt)
{
int j=q[i].to;
if(vis[j]) ans[q[i].num]=getf(j);
}
return;
}

对,这才是核心!

但我似乎并不知道从何讲起。

上半部分是把子节点遍历,下半部分是查找有关询问,记得控制\(vis[]\)数组。

这样,我们使用\(tarjan\)(太监) 算法,求\(LCA\)的时间复杂度就是\(O(n+q)\)了(\(n\)代表节点数,你也可以认为是边数,\(q\)是询问个数)。

\(ps\):我认为准确的复杂度是\(O(n\alpha(n)+q)\),不过不卡常可以认为是相等了。


再\(ps\)一下:有的大佬在存询问的结构体中设置记录该询问是否被回答的变量和记录相对空间的变量(就是记录那个询问存储与\(TA\)本质相同),其实都用处不大(跑满空间?),模拟后发现,在记录并判断\(vis[]\)的前提下,每个询问只会被回答\(1\)次,故这不多余(强者的世界我不懂,我还是算了吧)。

好了,似乎就这样讲完啦,理解背板子最重要呢(再回想下模拟过程?)。

LCA之tarjan离线的更多相关文章

  1. 洛谷 P3379 【模板】最近公共祖先(LCA)Tarjan离线

    题目链接:LCA tarjan离线 这道题目WA无数发,最后还是参考了大神的blog 谁会想到因为一个输入外挂WA呢 大概是我的挂是假挂吧...orz(其实加上外挂,速度提升很多) 用链式前向星保存边 ...

  2. hdu2586(lca模板 / tarjan离线 + RMQ在线)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2586 题意: 给出一棵 n 个节点的带边权的树, 有 m 个形如 x y 的询问, 要求输出所有 x, ...

  3. POJ 1330 Nearest Common Ancestors 【最近公共祖先LCA算法+Tarjan离线算法】

    Nearest Common Ancestors Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 20715   Accept ...

  4. POJ 1986 Distance Queries 【输入YY && LCA(Tarjan离线)】

    任意门:http://poj.org/problem?id=1986 Distance Queries Time Limit: 2000MS   Memory Limit: 30000K Total ...

  5. SPOJ 10628 Count on a tree(Tarjan离线LCA+主席树求树上第K小)

    COT - Count on a tree #tree You are given a tree with N nodes.The tree nodes are numbered from 1 to  ...

  6. LCA问题的ST,tarjan离线算法解法

    一  ST算法与LCA 介绍 第一次算法笔记这样的东西,以前学算法只是笔上画画写写,理解了下,刷几道题,其实都没深入理解,以后遇到新的算法要把自己的理解想法写下来,方便日后回顾嘛>=< R ...

  7. 【HDOJ2586】【Tarjan离线求LCA】

    http://acm.hdu.edu.cn/showproblem.php?pid=2586 How far away ? Time Limit: 2000/1000 MS (Java/Others) ...

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

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

  9. LCA(最近公共祖先)--tarjan离线算法 hdu 2586

    HDU 2586 How far away ? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/ ...

随机推荐

  1. 虚拟机与ubuntu系统的安装与基础操作

    1.虚拟机的下载: 常见的虚拟机软件有:VMware  VirtuaIBOX  Virtual PC  等. 这里主要介绍VMware ,VMware目前已经有很多个版本,可以根据个人情况进行选择.安 ...

  2. spark实验(二)--scala安装(1)

    一.实验目的 (1)掌握在 Linux 虚拟机中安装 Hadoop 和 Spark 的方法: (2)熟悉 HDFS 的基本使用方法: (3)掌握使用 Spark 访问本地文件和 HDFS 文件的方法. ...

  3. PAT T1017 The Best Peak Shape

    动态规划找最长上升子序列,正反遍历一遍序列即可~ #include<bits/stdc++.h> using namespace std; ; int N; int a[maxn]; in ...

  4. java动态代理中的invoke方法是如何被自动调用的

    转载声明:本文转载至 zcc_0015的专栏 一.动态代理与静态代理的区别. (1)Proxy类的代码被固定下来,不会因为业务的逐渐庞大而庞大:(2)可以实现AOP编程,这是静态代理无法实现的:(3) ...

  5. Netsparker破解版5.3 Netsparker Enterprise 5.3.0.24388[cracked]

    Netsparker破解版5.3 Netsparker Enterprise 5.3.0.24388[cracked]该版本更新时间为2019年7月8日下载地址:1 https://www.dr-fa ...

  6. luogu P1044 火车进出栈问题(Catalan数)

    Catalan数就是魔法 火车进出栈问题即: 一个栈(无穷大)的进栈序列为 1,2,3,4,...,n 求有多少个不同的出栈序列? 将问题进行抽象, 假设'+'代表进栈, 则有'-'代表出栈 那么如果 ...

  7. Java-读取.properties配置文件空指针异常java.lang.NullPointerException解决方法

    异常显示: 问题所在: 加载.properties配置文件路径多了一个斜杠 "/" : 解决方法: 去掉斜杠,重新启动程序即可. 总结: 遇到问题还是要认真阅读异常信息!

  8. JavaScript数组用法

    本文介绍一些js数组的用法: 上图的要点为: 1.unshift增加数组头部的元素,shift删除数组头部的元素. 2.delete除可删除对象的属性外,还可以删除数组的元素,使其占位变为undefi ...

  9. JS回弹原理-高级

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. Steam游戏《Zengeon(神明在上)》修改器制作-[先使用CE写,之后有时间的话改用C#](2020年寒假小目标06)

    日期:2020.01.30 博客期:138 星期四 [温馨提示]: 只是想要修改器的网友,可以直接点击此链接下载: 只是想拿CT文件的网友,可以直接点击此链接下载: 没有博客园账号的网友,可以将页面下 ...