浅谈Tarjan算法求LCA

来源:互联网 发布:淘宝天猫店有保障吗 编辑:程序博客网 时间:2024/05/21 05:19

Tarjan是一个很厉害的人,不少算法(包括一些数据结构比如splay)都是他发明的…


Tarjan求LCA是利用并查集的思想进行操作的

首先我们有如下的思路

void Tarjan(int u){    fa[u]=u;    for(register int i=head[u];i;i=line[i].nxt){        int v=line[i].to;        if(v!=father[u]){            Tarjan(v);            fa[v]=u;        }    }    for(register int i=head_question[u];i;i=question[i].nxt){        int v=question[i].to;        if(!fa[v]) question[i].lca=find(v);    }}

如果没有学过Tarjan求LCA的朋友看这里可能会有一些看不懂,现在我来解释一下其中的变量和数组的含义已经算法的大概思路


数据结构讲解:

首先fa数组表示的并查集中的那个fa,而father数组是在先前的dfs遍历的时候处理出来了的树上的fatherhead数组和line数组存的是树形结构的边(这里是邻接表结构),而headquestionquestion存的是问题的那条链的首尾处(也就是询问的那两个点的LCA),question[i].lca存的是该询问的lca,那么显然这两个数据结构应该是这样的

struct Line{    int val,from,to,nxt;};

以及

struct question{    int from,to,lca,nxt;};

显然这是一个离线算法,也就是说,我需要把所有的处理先离线到结构体中


算法讲解:

现在了解了数据结构之后我们来看看算法的内容以及实现,每次访问到一个数,我们就把其并查集中的fa设置为自己,这与普通并查集的操作是一样的,然后进行向下的访问,只要不往回访问,则往下继续递归操作,回来的时候把v的fa重置为u,也是并查集的操作,表示v可以追溯到u。
对于u的所有子点及子点的子孙已经访问完了之后,我们枚举所有和u有关的询问,试想一张图

这里写图片描述

假设我们的询问是

P-Q,P-B,A-Q

其中点上的数字是我们访问的dfs序,显然我们在处理Q的时候会递归处理到所有的Q的子孙,那么如果是在Qdfs树中处理到的点,我们发现无论如何其子点和Q点的fa都不可能高过Q点,因为我们进这一层dfs树的时候,Q点的fa数组置成了Q点本身,所以我们在访问其子点P点的时候,会发现Q点已经被访问过了,而P点是Q的子树中的点,所以他们的LCA就是当前Q所能追溯到的最早的fa,从P点往上走,到Q点的时候,枚举所有和Q有关系的询问,发现可以找到P点和A点,对于P点,寻找P点和Q点的LCA,就要找P点的最早能追溯到的祖先
我们发现P点网上追溯的时候,最多只能追溯到Q点,因为Q点的fa还是自己本身没有变,所有没有影响,对于A点,我们发现A点还没有处理过,也就是说fa[A]==0这个时候A并没有在这个dfs树中,也没有在并查集这个数据结构中,所以我们并不能对其进行处理,继续往上走,然后往7号节点访问,访问B,然后访问B的所有子点,回来的时候,枚举和B有关的所有询问,发现和P点有连接,而P点可以追溯到的最早的祖先是1号节点,因为在3号节点访问P节点的时候,将P节点的fa赋成了3号节点,而3号节点及其子树访问完的时候3号节点的fa又变成了2号节点,2号节点及其子树访问完了之后,2号节点的fa又赋值成1号节点,所以利用并查集的性质,我们可以追溯到的最先的祖先应该是1号节点
而我们当前的dfs树也是最高只能访问到1号节点,所以是从1号节点往右访问,访问到的B节点,所以他们的LCA就应该是1号节点,讲到这里,Tarjan算法求LCA的大致过程就已经讲完了,那么TarjanLCA的完整版代码应该包括第一遍的dfsdfs序和father以及并查集的find函数


完整代码实现:

void dfs(int u,int fat,int cnt){    father[u]=fat;    dfn[u]=++timer;    deep[u]=cnt;    for(register int i=head[u];i;i=line[i].nxt){        int v=line[i].to;        if(v!=fat) dfs(v,u,cnt+line[i].val);    }}int find(int x){    return x=fa[x]?x:fa[x]=find(x);} void Tarjan(int u){    fa[u]=u;    for(register int i=head[u];i;i=line[i].nxt){        int v=line[i].to;        if(v!=father[u]){            Tarjan(v);            fa[v]=u;        }    }    for(register int i=head_question[u];i;i=question[i].nxt){        int v=question[i].to;        if(!fa[v]) question[i].lca=find(v);    }}

值得注意的是,第一个dfs函数中还可以顺便处理一些其他数据,比如说对于一些边有权值的题目,还可以顺便处理一个深度,也就是上述代码中的deep数组所存储的数据