Tarjan求LCA

LCA问题算是一类比较经典的树上的问题

做法比较多样

比如说暴力啊,倍增啊等等

今天在这里给大家讲一下tarjan算法!

tarjan求LCA是一种稳定高速的算法

时间复杂度能做到预处理O(n + m),查询O(1)

它的主要思想是dfs和并查集

1.输入数据,找出根节点(或输入的)并将图存起来

2.输入需要查找的每一对点(两个点),也存起来(也存成图)

3.从根节点开始向它的每一个孩子节点进行深搜

4.同时开一个bool类型的数组记录此节点是否搜索过

5.搜索到p节点时先将p标记为已经搜索过了

6.然后遍历所有与p相连的节点,并标记为已经搜索过了

7.接着将p的子节点和p合并(此处要用到并查集)

8.然后遍历所有和p有询问关系的p的子节点

9.若该子节点已经遍历过,则一定可以将该子节点和p的父亲节点合并

可能还是有很多人并没有完全理解这段文字叙述的算法过程

下面就直接上代码(注释很详细)

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<cmath>
#include<string>
#include<iostream>
#include<algorithm>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<vector>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 5;
int read()
{
    int ans = 0, op = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-') op = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        ans *= 10;
        ans += ch - '0';
        ch = getchar();
    }
    return ans * op;
}
struct Drug
{
    int next, to, lca;
}edge[maxn<<1], qedge[maxn<<1];//edge[N]为树的链表;qedge[N]为需要查询LCA的两节点的链表
int n, m, s, x, y;
int num_edge, num_qedge, head[maxn], qhead[maxn], father[maxn];
bool visit[maxn];//判断是否被找过 
void add_edge(int from, int to)//建立树的链表 
{
    edge[++num_edge].next = head[from];
    edge[num_edge].to = to;
    head[from] = num_edge;
//    printf("#%d #%d #%d #%d\n", num_edge, head[from], from, edge[num_edge].next);
}
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 x)//找爹函数 
{
    if(father[x] ^ x) father[x] = find(father[x]);
    return father[x];
}
void dfs(int x)//把整棵树的一部分看作以节点x为根节点的小树, x的初始值为s; 
{
    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 & 1) qedge[k + 1].lca = qedge[k].lca;
            //由于将每一组查询变为两组,所以2n-1和2n的结果是一样的
            else qedge[k - 1].lca = qedge[k].lca;
        }
    }
}
int main(){
    n = read(), m = read(), s = read();//输入节点数,查询数和根节点 
    for(int i = 1;i < n;i++)
    {
        x = read(), y = read();//输入每条边 
        add_edge(x, y);
        add_edge(y, x);
    }
    for(int i = 1;i <= m;i++)
    {
        x = read(), y = read();
        //输入每次查询,考虑(u,v)时若查找到u但v未被查找,所以将(u,v)(v,u)全部记录 
        add_qedge(x, y);
        add_qedge(y, x);
    }
    dfs(s); 
    for(int i = 1;i <= m;i++) printf("%d\n", qedge[i << 1].lca);//两者结果一样,只输出一组即可
//    printf("%d", num_edge); 
    return 0;
}

 

 

 

 

posted @ 2018-10-10 17:34  thx666  阅读(204)  评论(0编辑  收藏  举报