原理可以参考大神

LCA_Tarjan (离线)

  TarjanTarjan 算法求 LCA 的时间复杂度为 O(n+q) ,是一种离线算法,要用到并查集。(注:这里的复杂度其实应该不是 O(n+q) ,还需要考虑并查集操作的复杂度 ,但是由于在多数情况下,路径压缩并查集的单次操作复杂度可以看做 O(1),所以写成了 O(n+q)。)

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + ;
struct EDGE{ int v, nxt, w; }Edge[maxn<<];
struct Query{ int v, id;
Query(){};
Query(int _v, int _id):v(_v),id(_id){};
}; vector<Query> q[maxn]; int Head[maxn], cnt;
int Fa[maxn];///并查集数组
int ans[maxn];///问询数数组大小要注意一下、不一定是 maxn
bool vis[maxn];///Tarjan算法中的标记数组
int n, m, s, qNum;///点、边、Tarjan递归起点、问询数 inline void init()
{
memset(Head, -, sizeof(Head));
memset(vis, false, sizeof(vis));
cnt = ;
} inline void AddEdge(int from, int to)
{
Edge[cnt].v = to;
Edge[cnt].nxt = Head[from];
Head[from] = cnt++;
} int Findset(int x)
{
int root = x;
while(Fa[root] != root) root = Fa[root]; int tmp;
while(Fa[x] != root){
tmp = Fa[x];
Fa[x] = root;
x = tmp;
} return root;
} void Tarjan(int v, int f)
{
Fa[v] = v;
for(int i=Head[v]; i!=-; i=Edge[i].nxt){
int Eiv = Edge[i].v;
if(Eiv == f) continue;
Tarjan(Eiv, v);
Fa[Findset(Eiv)] = v;
}
vis[v] = true;
for(int i=; i<q[v].size(); i++){
if(vis[q[v][i].v])
ans[q[v][i].id] = Findset(q[v][i].v);
}
} int main(void)
{
init();
scanf("%d %d %d %d", &n, &m, &s, &qNum);
for(int i=; i<=m; i++){
int u, v;
scanf("%d %d", &u, &v);
AddEdge(u, v);
AddEdge(v, u);
}
for(int i=; i<q; i++){
int u, v;
scanf("%d %d", &u, &v);
q[u].push_back(Query(v, i));
q[v].push_back(Query(u, i));
}
Tarjan(s, -);
for(int i=; i<q; i++) printf("%d\n", ans[i]);
return ;
}

倍增

  我们可以用倍增来在线求 LCA ,时间和空间复杂度分别是 O((n+q)logn) 和 O(nlogn) 。

  对于这个算法,我们从最暴力的算法开始:

    ①如果 aa 和 bb 深度不同,先把深度调浅,使他变得和浅的那个一样

    ②现在已经保证了 aa 和 bb 的深度一样,所以我们只要把两个一起一步一步往上移动,直到他们到达同一个节点,也就是他们的最近公共祖先了。

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
const int N=+;
vector <int> son[N];
int T,n,depth[N],fa[N],in[N],a,b;
void dfs(int prev,int rt){
depth[rt]=depth[prev]+;
fa[rt]=prev;
for (int i=;i<son[rt].size();i++)
dfs(rt,son[rt][i]);
}
int LCA(int a,int b){
if (depth[a]>depth[b])
swap(a,b);
while (depth[b]>depth[a])
b=fa[b];
while (a!=b)
a=fa[a],b=fa[b];
return a;
}
int main(){
scanf("%d",&T);
while (T--){
scanf("%d",&n);
for (int i=;i<=n;i++)
son[i].clear();
memset(in,,sizeof in);
for (int i=;i<n;i++){
scanf("%d%d",&a,&b);
son[a].push_back(b);
in[b]++;
}
depth[]=-;
int rt=;
for (int i=;i<=n&&rt==;i++)
if (in[i]==)
rt=i;
dfs(,rt);
scanf("%d%d",&a,&b);
printf("%d\n",LCA(a,b));
}
return ;
}

优化

 1. 把 aa 和 bb 移到同一深度(设 depthx 为节点 x 的深度),假设 deptha≤depthbdeptha≤depthb ,这个时候,之前预处理的 fafa 数组就派上用场了。从大到小枚举 kk ,如果 bb 向上跳 2k2k 得到的节点的深度 ≥deptha≥deptha ,那么 bb 就往上跳。

  2.如果 a=ba=b ,那么显然 LCA 就是 aa。否则执行第 3 步。

  3.这一步的主要目的是 :分别找到最浅的 a′a′ 和 b′b′ ,并且 a′≠b′a′≠b′ 。

    利用之前的那个性质,再利用倍增,从大到小枚举 kk ,如果对于当前的 kk , aa 和 bb 的第 2k个祖先不同,那么 aa 和 bb 都跳到其 2k 祖先的位置。LCA 就是 faa′,0或者 fab′,0。

#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <vector>
using namespace std;
const int N=+;
vector <int> son[N];
int T,n,depth[N],fa[N][],in[N],a,b;
void dfs(int prev,int rt){
depth[rt]=depth[prev]+;
fa[rt][]=prev;
for (int i=;i<;i++)
fa[rt][i]=fa[fa[rt][i-]][i-];
for (int i=;i<son[rt].size();i++)
dfs(rt,son[rt][i]);
}
int LCA(int x,int y){
if (depth[x]<depth[y])
swap(x,y);
for (int i=;i>=;i--)
if (depth[x]-(<<i)>=depth[y])
x=fa[x][i];
if (x==y)
return x;
for (int i=;i>=;i--)
if (fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];
return fa[x][];
}
int main(){
scanf("%d",&T);
while (T--){
scanf("%d",&n);
for (int i=;i<=n;i++)
son[i].clear();
memset(in,,sizeof in);
for (int i=;i<n;i++){
scanf("%d%d",&a,&b);
son[a].push_back(b);
in[b]++;
}
depth[]=-;
int rt=;
for (int i=;i<=n&&rt==;i++)
if (in[i]==)
rt=i;
dfs(,rt);
scanf("%d%d",&a,&b);
printf("%d\n",LCA(a,b));
}
return ;
}

RMQ

  现在来介绍一种 O(nlogn)O(nlog⁡n) 预处理,O(1)O(1) 在线查询的算法。

  RMQ 的意思大概是“区间最值查询”。顾名思义,用 RMQ 来求 LCA 是通过 RMQ 来实现的。

//CodeVS2370
#include <bits/stdc++.h>
#define time _____time
using namespace std;
const int N=;
struct Gragh{
int cnt,y[N*],z[N*],nxt[N*],fst[N];
void clear(){
cnt=;
memset(fst,,sizeof fst);
}
void add(int a,int b,int c){
y[++cnt]=b,z[cnt]=c,nxt[cnt]=fst[a],fst[a]=cnt;
}
}g;
int n,m,depth[N],in[N],out[N],time;
int ST[N*][];
void dfs(int x,int pre){
in[x]=++time;
ST[time][]=x;
for (int i=g.fst[x];i;i=g.nxt[i])
if (g.y[i]!=pre){
depth[g.y[i]]=depth[x]+g.z[i];
dfs(g.y[i],x);
ST[++time][]=x;
}
out[x]=time;
}
void Get_ST(int n){
for (int i=;i<=n;i++)
for (int j=;j<;j++){
ST[i][j]=ST[i][j-];
int v=i-(<<(j-));
if (v>&&depth[ST[v][j-]]<depth[ST[i][j]])
ST[i][j]=ST[v][j-];
}
}
int RMQ(int L,int R){
int val=floor(log(R-L+)/log());
int x=ST[L+(<<val)-][val],y=ST[R][val];
if (depth[x]<depth[y])
return x;
else
return y;
}
int main(){
scanf("%d",&n);
for (int i=,a,b,c;i<n;i++){
scanf("%d%d%d",&a,&b,&c);
a++,b++;
g.add(a,b,c);
g.add(b,a,c);
}
time=;
dfs(,);
depth[]=;
Get_ST(time);
scanf("%d",&m);
while (m--){
int x,y;
scanf("%d%d",&x,&y);
if (in[x+]>in[y+])
swap(x,y);
int LCA=RMQ(in[x+],in[y+]);
printf("%d\n",depth[x+]+depth[y+]-depth[LCA]*);
}
return ;
}

LCA最近公共祖先模板(求树上任意两个节点的最短距离 || 求两个点的路进(有且只有唯一的一条))的更多相关文章

  1. LCA(最近公共祖先)模板

    Tarjan版本 /* gyt Live up to every day */ #pragma comment(linker,"/STACK:1024000000,1024000000&qu ...

  2. LCA最近公共祖先模板代码

    vector模拟邻接表: #include<iostream> #include<cstdio> #include<cstring> #include<cma ...

  3. lca最短公共祖先模板(hdu2586)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2586 #include<iostream> #include<cstdio> ...

  4. LCA 最近公共祖先 (笔记、模板)

    求lca的方法大体有三种: 1.dfs+RMQ(线段树 ST表什么的) 在线 2.倍增 在线 3.tarjan 离线 ps:离线:所有查询全输入后一次解决 在线:有一个查询输出一次 以下模板题为 洛谷 ...

  5. 求LCA最近公共祖先的在线ST算法_C++

    ST算法是求最近公共祖先的一种 在线 算法,基于RMQ算法,本代码用双链树存树 预处理的时间复杂度是 O(nlog2n)   查询时间是 O(1) 的 另附上离线算法 Tarjan 的链接: http ...

  6. Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)

    Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ...

  7. LCA 最近公共祖先 tarjan离线 总结 结合3个例题

    在网上找了一些对tarjan算法解释较好的文章 并加入了自己的理解 LCA(Least Common Ancestor),顾名思义,是指在一棵树中,距离两个点最近的两者的公共节点.也就是说,在两个点通 ...

  8. LCA(最近公共祖先)之倍增算法

    概述 对于有根树T的两个结点u.v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u.v的祖先且x的深度尽可能大. 如图,3和5的最近公共祖先是1,5和2的最近公共祖先是4 在本篇中我们先介 ...

  9. CodeVs.1036 商务旅行 ( LCA 最近公共祖先 )

    CodeVs.1036 商务旅行 ( LCA 最近公共祖先 ) 题意分析 某首都城市的商人要经常到各城镇去做生意,他们按自己的路线去做,目的是为了更好的节约时间. 假设有N个城镇,首都编号为1,商人从 ...

随机推荐

  1. matlab读取excel

    xlsread函数: x = xlsread('d:/min1.csv','B2:B10');    %文件名和路径:所读取的数据范围:

  2. Android指针管理:RefBase,SP,WP

    Android中通过引用计数来实现智能指针,并且实现有强指针与弱指针.由对象本身来提供引用计数器,但是对象不会去维护引用计数器的值,而是由智能指针来管理. 要达到所有对象都可用引用计数器实现智能指针管 ...

  3. 4-5 父节点watcher事件

    三种方式设置watcher:ls.stat.get

  4. 1-1+zookeeper简介

     zookeeper是中间件,可以为分布式系统提供协调服务.如果没有分布式系统,zookeeper也发挥不了它的优势.

  5. Codeforces 719E (线段树教做人系列) 线段树维护矩阵

    题面简洁明了,一看就懂 做了这个题之后,才知道怎么用线段树维护递推式.递推式的递推过程可以看作两个矩阵相乘,假设矩阵A是初始值矩阵,矩阵B是变换矩阵,求第n项相当于把矩阵B乘了n - 1次. 那么我们 ...

  6. c++的单例模式及c++11对单例模式的优化

    单例模式 单例模式,可以说设计模式中最常应用的一种模式了,据说也是面试官最喜欢的题目.但是如果没有学过设计模式的人,可能不会想到要去应用单例模式,面对单例模式适用的情况,可能会优先考虑使用全局或者静态 ...

  7. HDOJ 4802 GPA

    Problem Description In college, a student may take several courses. for each course i, he earns a ce ...

  8. cJSON

    http://blog.csdn.net/wangchangshuai0010/article/details/18225423

  9. 符合条件中用where 1=1影响效率以及having和where的区别

    想当初我自己想出来用where 1=1的时候还高兴了一小会,毕竟把代码简化了许多.今天看到的书里面说会影响性能.摘要如下: 低效的“WHERE 1=1” 网上有不少人提出过类似的问题:“看到有人写了W ...

  10. unity list循环

    复制的感觉挺有用就保存下来 using System.Collections;using System.Collections.Generic;using UnityEngine; public cl ...