蒟蒻林荫小复习——Tarjan求LCA

由于林荫要准备CSP-S(拿不到的话就可能要去郸城一高了),因此开始复习以前写过的所有算法。

在此先致敬一下Robert Tarjan,这位老先生现在仍在为人类做贡献。

Tarjan求LCA算法属于离线算法,要求事先得知所要求解的点对。

算法由一个DFS组成,伪代码大意就是:

DFS(x)

{

  1.对于该点打上访问标记

  2.将该点的父节点置为自己

       3.访问这个点的每一个子节点,对每个子节点进行DFS

  4.将子节点的父亲置为自己

  5.对每一个关于这个节点的询问进行遍历

    6.如果这个点的对应节点已经被访问,那么答案就是对应节点的最终祖先否则跳过。

}

这样的话就是一个简单的DFS就可以结束了。

代码放一下:

int dfs(int x){//把整棵树的一部分看作以节点x为根节点的小树 
    father[x]=x;//由于节点x被看作是根节点,所以把x的father设为它自己 
    visit[x]=1;//标记为已被搜索过 
    for(int k=head[x];k;k=edge[k].next)//遍历所有与x相连的节点 
        if(!visit[edge[k].to]){//若未被搜索 
            dfs(edge[k].to);//以该节点为根节点搞小树 
            father[edge[k].to]=x;//把x的孩子节点的father重新设为x 
        }
    for(int k=qhead[x];k;k=qedge[k].next)//搜索包含节点x的所有询问 
        if(visit[qedge[k].to]){//如果另一节点已被搜索过 
            qedge[k].lca=find(qedge[k].to);//把另一节点的祖先设为这两个节点的最近公共祖先 
            if(k%2)//由于将每一组查询变为两组,所以2n-1和2n的结果是一样的 
                qedge[k+1].lca=qedge[k].lca;
            else
                qedge[k-1].lca=qedge[k].lca;
        }
}

很简单对吧!

正确性证明:

先放张图:鸣谢林荫的好队友兼摄影师一只jinx大佬哈。

 圈内的编号是点的序号,点上被划掉的数字是这个点的父亲曾经所存的值,没被划掉的是最后一次存下的父亲的值。

图中少修改了一次值,2节点上面的2应当被划掉修改成1

仔细看图可以确定现在DFS进行到了哪里?

仔细看图,看图,看图!!!

没错,是走到了7号节点,也就是说,当前最内层DFS的x值是7.

我们很容易的可以知道,现在每个节点都已经被标记了。假定有询问点对1——7,3——7,6——7,5——7。

我们一个一个看,对于1节点,因为已经被访问过,LCA即为1的父亲。对于3节点,LCA=Lastfa[3]=Lastfa[2]=Lastfa[1]=1.

对于6——7,LCA=Lastfa[6]=5,对于5——7同理。

因为当一个点的对应点已经被访问过之后,代表两点一定在同一条链,或者两点不在同一颗子树上。因为每一次对于某个点的子节点全部访问完毕后,子节点的父亲都会被置为这个点,否则不变。也就是说,对应点的父节点的Lastfa一定是遍历进入这个点之前所访问过的深度最小的点。  大家可以将其理解为爬山一样,先在山脚找到点对中第一个点,然后向上爬,爬到一个可以到达第二个点的地方。因为DFS是从上向下找,到最底部之后再开始一点一点向上爬,找到最近的岔路口之后就向岔路口走去,也就是说我们在爬山过程中找到的一定是LCA。

最后放个代码吧。

#include<iostream>
#include<cstdio>
#define N 1000001
struct hehe{
    int next;
    int to;
    int lca;
};
hehe edge[N];//树的链表
hehe qedge[N];//需要查询LCA的两节点的链表
int n,m,p,x,y;
int num_edge,num_qedge,head[N],qhead[N];
int father[N];
int visit[N];//判断是否被找过 
void add_edge(int from,int to){//建立树的链表 
    edge[++num_edge].next=head[from];
    edge[num_edge].to=to;
    head[from]=num_edge;
}
void add_qedge(int from,int to){//建立需要查询LCA的两节点的链表 
    qedge[++num_qedge].next=qhead[from];
    qedge[num_qedge].to=to;
    qhead[from]=num_qedge;
}
int find(int z){//找爹函数 
    if(father[z]!=z)
        father[z]=find(father[z]);
    return father[z];
}
int dfs(int x){//把整棵树的一部分看作以节点x为根节点的小树 
    father[x]=x;//由于节点x被看作是根节点,所以把x的father设为它自己 
    visit[x]=1;//标记为已被搜索过 
    for(int k=head[x];k;k=edge[k].next)//遍历所有与x相连的节点 
        if(!visit[edge[k].to]){//若未被搜索 
            dfs(edge[k].to);//以该节点为根节点搞小树 
            father[edge[k].to]=x;//把x的孩子节点的father重新设为x 
        }
    for(int k=qhead[x];k;k=qedge[k].next)//搜索包含节点x的所有询问 
        if(visit[qedge[k].to]){//如果另一节点已被搜索过 
            qedge[k].lca=find(qedge[k].to);//把另一节点的祖先设为这两个节点的最近公共祖先 
            if(k%2)//由于将每一组查询变为两组,所以2n-1和2n的结果是一样的 
                qedge[k+1].lca=qedge[k].lca;
            else
                qedge[k-1].lca=qedge[k].lca;
        }
}
int main(){
    scanf("%d%d%d",&n,&m,&p);//输入节点数,查询数和根节点 
    for(int i=1;i<n;++i){
        scanf("%d%d",&x,&y);//输入每条边 
        add_edge(x,y);
        add_edge(y,x);
    }
    for(int i=1;i<=m;++i){
        scanf("%d%d",&x,&y);//输入每次查询,考虑(u,v)时若查找到u但v未被查找,所以将(u,v)(v,u)全部记录 
        add_qedge(x,y);
        add_qedge(y,x);
    }
    dfs(p);//进入以p为根节点的树的深搜 
    for(int i=1;i<=m;i++)
        printf("%d\n",qedge[i*2].lca);//两者结果一样,只输出一组即可 
    return 0;
}

完结撒花!!!

猜你喜欢

转载自www.cnblogs.com/XLINYIN/p/11788729.html