【在线】

1.倍增法

现将深度较大的跳至与深度较小的统一深度。预处理$fa[u][i]$表示$u$往上跳$2^i$个单位后的祖先,则就可以像快速幂一样,将移动的步数化为二进制,如果第$i$位为$1$,那么向上跳$2^i$次方,即$if(1 << i \& d) u = fa[u][i]$。跳至统一深度后,若两点重合,则返回两点的任意一个。若不重合,再一个一个一起往上跳,直到重合。

复杂度为$O(N*logN) $

【code】求两点距离

#include<iostream>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstdlib>
using namespace std; const int N = ;
int dep[N], dis[N];
int ecnt, adj[N], go[N << ], len[N << ], nxt[N << ];
int fa[N][], Log[N], n, m; inline void addEdge(const int &u, const int &v, const int &l){
nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v, len[ecnt] = l;
nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u, len[ecnt] = l;
} inline void Init_Log(){
Log[] = -;
for(int i = ; i <= n; i++) Log[i] = Log[i >> ] + ;
} inline void dfs(const int &u, const int &f, const int &l){
dep[u] = dep[f] + ;
dis[u] = dis[f] + l;
fa[u][] = f;
for(int i = ; fa[u][i]; i++)
fa[u][i + ] = fa[fa[u][i]][i];
for(int e = adj[u]; e; e = nxt[e]){
int v = go[e];
if(v == f) continue;
dfs(v, u, len[e]);
}
} inline int lca(int u, int v){
if(dep[u] < dep[v]) swap(u, v);
int delta = dep[u] - dep[v];
for(int i = Log[delta]; i >= ; i--)
if( << i & delta) u = fa[u][i];
if(u == v) return u;
for(int i = Log[dep[u]]; i >= ; i--)
if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
return fa[u][];
} int main(){
scanf("%d%d", &n, &m);
Init_Log();
for(int i = ; i < n; i++){
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
addEdge(x, y, z);
}
dfs(,,);
for(int i = ; i <= m; i++){
int x, y;
scanf("%d%d", &x, &y);
int L = lca(x, y);
cout<<(dis[x] - dis[L]) + (dis[y] - dis[L])<<endl;
}
return ;
}

2.树链剖分

  树链剖分

  同样,将点往上跳,不过树链剖分后可以直接从重链尾部跳到重链顶部甚至下一条重链的尾部,直到两点在同一重链上,先判重合,否则就是现在深度较小的点。

  复杂度O(mlog2 n)

  【code】求两点距离

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
using namespace std; const int N = 1e5 + ;
const int oo = 0x3f3f3f3f; int dep[N], sze[N], top[N], son[N], pos[N], idx[N], val[N], fa[N];
int ecnt, adj[N], go[N << ], nxt[N << ], tot, len[N << ];
int n, m, dis[N]; inline int Re(){
int i = , f = ; char ch = getchar();
for(; (ch < '' || ch > '') && ch != '-'; ch = getchar());
if(ch == '-') f = -, ch = getchar();
for(; ch >= '' && ch <= ''; ch = getchar())
i = (i << ) + (i << ) + (ch - '');
return i * f;
} inline void Wr(int x){
if(x < ) putchar('-'), x = -x;
if(x > ) Wr(x / );
putchar(x % + '');
} inline void addEdge(const int &u, const int &v, const int &l){
nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v, len[ecnt] = l;
nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u, len[ecnt] = l;
} inline void dfs1(const int &u, const int &f, const int &l){
dep[u] = dep[f] + ;
dis[u] = dis[f] + l;
fa[u] = f;
sze[u] = ;
for(int e = adj[u]; e; e = nxt[e]){
int v = go[e];
if(v == f) continue;
dfs1(v, u, len[e]);
sze[u] += sze[v];
if(sze[v] > sze[son[u]]) son[u] = v;
}
} inline void dfs2(const int &u, const int &f){
if(son[u]){ //先查重儿子, 保证重链连续
top[son[u]] = top[u];
idx[pos[son[u]] = ++tot] = son[u];
dfs2(son[u], u);
}
for(int e = adj[u]; e; e = nxt[e]){
int v = go[e];
if(v == f || v == son[u]) continue;
top[v] = v;
idx[pos[v] = ++tot] = v;
dfs2(v, u);
}
} inline int lca(int u, int v){
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]]) swap(u, v);
u = fa[top[u]];
}
if(u == v) return u;
if(dep[u] < dep[v]) return u;
else return v;
} int main(){
// freopen("h.in", "r", stdin);
n = Re(), m = Re();;
for(int i = ; i < n; i++){
int a = Re(), b = Re(), c = Re();
addEdge(a, b, c);
}
dfs1(, , );
pos[] = top[] = idx[] = tot = , dep[] = -;
dfs2(, );
for(int i = ; i <= m; i++){
int a = Re(), b = Re();
int L = lca(a, b);
Wr(dis[a] + dis[b] - * dis[L]), putchar('\n');
}
return ;
}

【离线】

【tarjan】

  奇妙的算法。但要求必须离线,先记录下所有的询问,再挨个找到答案。

  用$dfs$的思想,现将子树扫描完,再返回根节点,进入下一颗子树。

  tarjan求lca则每扫描完一颗子树,就将他与根节点的并查集进行合并,然后处理有关询问(可能现在还没法回答)。

  如下图所示:比如我要查找$(4, 5), (4, 6)$的$lca$

  扫描完$4$这颗子树后,4的并查集祖先已经设置为2,就可以开始尝试处理$4$中 的询问了

但是,处理4-6, 4-5询问时,发现5、6还没被访问到,所以回答失败,继续dfs。

        

  扫描完5,处理询问,发现4已经访问过,而我dfs时从4返回到2,4的并查集祖先已经设置为2,然后我跨过2到达5,所以4和5的祖先一定就是getAnc(4) = 2.

  4-6同理,,2的祖先被设置为1,那么4通过并查集的维护最终祖先也变为1,跨过1后到达6,那么4-6的lca一定为1.

  看懂了这个算法,就明白为什么必须要求离线了。

  【code】求两点lca

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<vector>
using namespace std; #define mp make_pair
const int N = ;
int ecnt, adj[N], go[N << ], nxt[N << ], len[N << ];
int n, m, qa[N], qb[N], qans[N];
vector<pair<int, int> > vq[N];
int anc[N], dis[N];
bool vst[N]; int read(){
int i=,f=;char ch;
for(ch=getchar();(ch<''||ch>'')&&ch!='-';ch=getchar());
if(ch=='-') {f=-;ch=getchar();}
for(;ch>=''&&ch<='';ch=getchar()) i=(i<<)+(i<<)+(ch^);
return f*i;
} inline void wr(int x){
if(x < ) putchar('-'), x = -x;
if(x > ) wr(x / );
putchar(x % + '');
} inline void addEdge(int u, int v, int l){
nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v, len[ecnt] = l;
nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u, len[ecnt] = l;
} inline int getAnc(int u){
return (u == anc[u]) ? u : (anc[u] = getAnc(anc[u]));
} inline void Merge(int u, int v){
int fu = getAnc(u), fv = getAnc(v);
if(fu != fv) anc[fu] = fv;
} inline void tarjan(int u, int f, int d){
dis[u] = dis[f] + d;
for(int i = adj[u]; i; i = nxt[i]){
if(go[i] == f) continue;
if(!vst[go[i]]){
tarjan(go[i], u, len[i]);
Merge(go[i], u);
anc[getAnc(u)] = u;
}
}
vst[u] = true;
for(int i = ; i < vq[u].size(); i++){
if(vst[vq[u][i].first])
qans[vq[u][i].second] = getAnc(vq[u][i].first);
}
} int main(){
n = read(), m = read();
for(int i = ; i < n; i++){
int a, b, c;
a = read(), b = read(), c = read();
addEdge(a, b, c);
}
for(int i = ; i <= m; i++){
int u, v;
u = read(), v = read();
qa[i] = u, qb[i] = v;
vq[u].push_back(mp(v, i)), vq[v].push_back(mp(u, i));
}
for(int i = ; i <= n; i++) anc[i] = i;
tarjan(, , );
for(int i = ; i <= m; i++){
int ans = dis[qa[i]] + dis[qb[i]] - * dis[qans[i]];
wr(ans), putchar('\n');
}
return ;
}

【LCA最近公共祖先】在线离线的更多相关文章

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

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

  2. LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现

    首先是最近公共祖先的概念(什么是最近公共祖先?): 在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点. 换句话说,就是两个点在这棵 ...

  3. 求LCA最近公共祖先的离线Tarjan算法_C++

    这个Tarjan算法是求LCA的算法,不是那个强连通图的 它是 离线 算法,时间复杂度是 O(m+n),m 是询问数,n 是节点数 它的优点是比在线算法好写很多 不过有些题目是强制在线的,此类离线算法 ...

  4. LCA最近公共祖先 Tarjan离线算法

    学习博客:  http://noalgo.info/476.html 讲的很清楚! 对于一颗树,dfs遍历时,先向下遍历,并且用并查集维护当前节点和父节点的集合.这样如果关于当前节点(A)的关联节点( ...

  5. lca(最近公共祖先(离线))

    转自大佬博客 : https://www.cnblogs.com/JVxie/p/4854719.html   LCA 最近公共祖先 Tarjan(离线)算法的基本思路及其算法实现 首先是最近公共祖先 ...

  6. lca最近公共祖先与树上倍增。

    https://vjudge.net/contest/295298#problem/A lca 的题目 求任意两点的距离. A题是在线算法,用st表rmq来实现. https://blog.csdn. ...

  7. LCA 近期公共祖先 小结

    LCA 近期公共祖先 小结 以poj 1330为例.对LCA的3种经常使用的算法进行介绍,分别为 1. 离线tarjan 2. 基于倍增法的LCA 3. 基于RMQ的LCA 1. 离线tarjan / ...

  8. lca 最近公共祖先

    http://poj.org/problem?id=1330 #include<cstdio> #include<cstring> #include<algorithm& ...

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

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

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

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

随机推荐

  1. 各大免费邮箱邮件群发账户SMTP服务器配置及SMTP发送量限制情况

    网络产品推广和新闻消息推送时,经常用到的工具就是用客户邮箱发送邮件了,如果是要发送的邮件量非常大的话,一般的建议是搭建自己的邮局服务器,或者是花钱购买专业的邮件群发服务,免费邮箱的SMTP适合少量的邮 ...

  2. [D3] Drawing path in D3

    Here we have a force layout with three nodes. In the example, we will link three nodes with line and ...

  3. iOS开发RunLoop学习:一:RunLoop简单介绍

    一:RunLoop的简单介绍 #import "ViewController.h" @interface ViewController () @end @implementatio ...

  4. jemter--录制的脚本设置循环次数不起作用

    以下是比较jmeter线程组中设置循环次数和循环控制器中设置循环次数的区别 1.jmeter生成的脚本没有step1(循环控制器)控制器,故循环在线程组中设置   2.badboy录制的脚本有setp ...

  5. BUFSIZ

    转http://www.judymax.com/archives/262 今天在看示例程序时冒出来一句args = emalloc(BUFSIZ); BUFSIZ是什么意思,查了一下才明白. 这是st ...

  6. 【重拾Effective Java】一

    之前看这本<Effective Java(第二版)>都是非常早曾经了.这本书确实是本好书.须要细嚼慢咽,每次看都有不同的体验. 在此写博客巩固一下. 第一章.创建和销毁对象 考虑用静态工厂 ...

  7. javascrit开发的基本代码结构的

    今天看到群里一个demo,简单看了一下. 然后自己就写了一个通用的javascrit开发的基本代码结构的js文件. 代码例如以下: (function($,win){ //定义全局变量对象 var o ...

  8. 运行一个Hadoop Job所需要指定的属性 分类: A1_HADOOP 2015-02-02 21:33 231人阅读 评论(0) 收藏

    1.设置job的基础属性 Job job = new Job(); job.setJarByClass(***.class); job.setJobName("job name") ...

  9. iOS开发Quzrtz2D:十一:图片截屏以及图片擦除

    一:图片截屏:截取的是控制器的view #import "ViewController.h" @interface ViewController () @end @implemen ...

  10. 配置java 环境变量(jdk)

    java环境变量需要配置3个: JAVA_HOME:D:\Program Files (x86)\Java\jdk1.8 CLASSPATH:.;%JAVA_HOME%\lib\dt.jar;%JAV ...