tarjan求lca的神奇

题目描述

如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

输入输出格式

输入格式:

第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。

接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。

输出格式:

输出包含M行,每行包含一个正整数,依次为每一个询问的结果。

输入输出样例

输入样例#1:

5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5

输出样例#1:

4
4
1
4
4

说明

时空限制:1000ms,128M

数据规模:

对于30%的数据:N<=10,M<=10

对于70%的数据:N<=10000,M<=10000

对于100%的数据:N<=500000,M<=500000

~~~~~~~~~~~~~~~华丽的分割线~~~~~~~~~~~~~~~

对于此题,常规解法就是暴力。

↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑

炸死你。

倍增,可以有效降低复杂度,我也是学了两次之后掌握了它。

这里,我想记录下不久前学习的tarjan求lca

(tarjan真是好东西)

说它是tarjan,其实给我的感觉就是套了一个tarjan的名字罢了。(类似我的dijksPA)其实它就是一个dfs的神奇过程。

先放代码再说

#include<bits/stdc++.h>
using namespace std;
const int maxn=500005;
int n,m,p;
struct edge
{
    int next,to,lca;
}e[maxn<<1],e1[maxn<<1];
int head[maxn<<1],cnt,head1[maxn<<1];
inline int addedge(int from,int to)//前向星存图
{
    e[++cnt].next=head[from];
    e[cnt].to=to;
    head[from]=cnt;
}
inline int add (int from,int to)//前向星存储查询
{
    e1[++cnt].next=head1[from];
    e1[cnt].to=to;
    head1[from]=cnt;
}
int f[maxn<<1],vis[maxn<<1];
int find(int x)//并查集找爹函数
{
    if(f[x]!=x)
    f[x]=find(f[x]);
    return f[x];
}
int tarjan(int u)//神奇的dfs
{
    f[u]=u;//爸爸
    vis[u]=1;//走过
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(!vis[v])
        {
            tarjan(v);
            f[v]=u;//爹
        }
    }
    for(int i=head1[u];i;i=e1[i].next)//询问的图
    {
        int v=e1[i].to;
        if(vis[v])
        {
            e1[i].lca=find(v);
            if(i%2==1)
            e1[i+1].lca=e1[i].lca;//
            else
            e1[i-1].lca=e1[i].lca;
        }
    }
}

int main()
{
    scanf("%d%d%d",&n,&m,&p);
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        addedge(x,y);
        addedge(y,x);
    }
    for(int i=1;i<=n;i++)
    f[i]=i;//初始化并查集
    cnt=0;//因为上面村边用过cnt,所以把它拍成0
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    tarjan(p);//从根节点开始dfs
    for(int i=1;i<=m;i++)
    {
        printf("%d\n",e1[i*2].lca);
    }
    return 0;
}

      1.任选一个点为根节点,从根节点开始。

      2.遍历该点u所有子节点v,并标记这些子节点v已被访问过。

      3.若是v还有子节点,返回2,否则下一步。

      4.合并v到u上。

      5.寻找与当前点u有询问关系的点v。

      6.若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。

https://www.cnblogs.com/JVxie/p/4854719.html这位大佬的模拟过程真的很强

总的来说,感觉这个算法不是太常用吧。大数据的情况下可能没有倍增快,码量也没小到那里去,而且到现在我还是懵懵的。

写个博客记录一下吧。

原文地址:https://www.cnblogs.com/ajmddzp/p/10747825.html

时间: 2024-10-06 15:35:29

tarjan求lca的神奇的相关文章

【最近公共祖先Tarjan】Tarjan求LCA练习

Tarjan求LCA 这是一篇非常好的讲解,靠这个文章搞懂的~ 1 void tarjan(int u) 2 { 3 vis[u]=1; 4 for(int i=0;i<edge[u].size();i++) 5 { 6 int v=edge[u][i]; 7 if(vis[v] == 0) 8 { 9 tarjan(v); 10 p[v]=u; 11 } 12 } 13 for(int i=0;i<qy[u].size();i++) 14 { 15 int v=qy[u][i].v,id=q

poj 1470 Closest Common Ancestors tarjan求lca和树的孩子兄弟表示

题意: 给一棵树和若干查询点对,求这些点对的lca. 分析: tarjan求lca的模板题,树还是用孩子兄弟表示法比较简洁. 代码: //poj 1470 //sepNINE #include <iostream> #include <vector> using namespace std; const int maxN=1024; int n,u,v,t,m,x,y;; int par[maxN],son[maxN],bro[maxN],f[maxN],cnt[maxN],vis

Tarjan求LCA

LCA问题算是一类比较经典的树上的问题 做法比较多样 比如说暴力啊,倍增啊等等 今天在这里给大家讲一下tarjan算法! tarjan求LCA是一种稳定高速的算法 时间复杂度能做到预处理O(n + m),查询O(1) 它的主要思想是dfs和并查集 1.输入数据,找出根节点(或输入的)并将图存起来 2.输入需要查找的每一对点(两个点),也存起来(也存成图) 3.从根节点开始向它的每一个孩子节点进行深搜 4.同时开一个bool类型的数组记录此节点是否搜索过 5.搜索到p节点时先将p标记为已经搜索过了

tarjan求lca :并查集+dfs

//参考博客 https://www.cnblogs.com/jsawz/p/6723221.html#include<bits/stdc++.h> using namespace std; #define maxn 420000 struct Query{int to,nxt,lca;}q[maxn]; struct Edge{int to,nxt;}edge[maxn<<1]; int n,m,p,x,y,tot_e,tot_q,head_q[maxn],head_e[maxn

tarjan求LCA的体会

#include<iostream> #include<cstdio> #include<cstring> #include<vector> #include<algorithm> using namespace std; const int N=500005; int n,m,s,cnt,num;//树的结点个数.询问的个数和树根结点的序号. struct EDGE{ int v,next; }; struct QUE{ int node,ne

Tarjan求LCA模板

1 int n,m,hcnt,qcnt; 2 int sx,sy; 3 struct Node{ 4 int to,next; 5 }node[maxn]; 6 struct Query{ 7 int x,ne; 8 int i; 9 }query[maxn<<1]; 10 int head[maxn],vis[maxn],indu[maxn]; 11 int fp[maxn],anc[maxn],que[maxn],ans[maxn<<1]; 12 int findp(int x

tarjan,树剖,倍增求lca

1.tarjan求lca Tarjan(u)//marge和find为并查集合并函数和查找函数 { for each(u,v) //访问所有u子节点v { Tarjan(v); //继续往下遍历 marge(u,v); //合并v到u上 标记v被访问过; } for each(u,e) //访问所有和u有询问关系的e { 如果e被访问过; u,e的最近公共祖先为find(e); } } 2.倍增lca(在线) #include<bits/stdc++.h> using namespace st

Tarjan之求LCA

Tarjan之求LCA 不要问我为什么写完Tarjan还要再补一句"求LCA的那个" 因为只说Tarjan的话完全不知道你指的是哪个算法-- 劳模Tarjan同志证明了好多算法,而且全都叫Tarjan算法(是不会起名了吗x) 这个Tarjan是一个求LCA的离线算法 关于什么是在线什么是离线-- "所谓的在线算法就是实时性的,比方说,给你一个输入,算法就给出一个输出,就像是http请求,请求网页 一样.给一个实时的请求,就返回给你一个请求的网页.而离线算法则是要求一次性读入所

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 N.Each node has an integer weight. We will ask you to perform the following operation: u v k : ask for the kth minimum weight on the path from node u