【在线】

1.倍增法

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

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

【code】求两点距离

  1. #include<iostream>
  2. #include<cstring>
  3. #include<string>
  4. #include<algorithm>
  5. #include<cmath>
  6. #include<cstdio>
  7. #include<cstdlib>
  8. using namespace std;
  9.  
  10. const int N = ;
  11. int dep[N], dis[N];
  12. int ecnt, adj[N], go[N << ], len[N << ], nxt[N << ];
  13. int fa[N][], Log[N], n, m;
  14.  
  15. inline void addEdge(const int &u, const int &v, const int &l){
  16. nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v, len[ecnt] = l;
  17. nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u, len[ecnt] = l;
  18. }
  19.  
  20. inline void Init_Log(){
  21. Log[] = -;
  22. for(int i = ; i <= n; i++) Log[i] = Log[i >> ] + ;
  23. }
  24.  
  25. inline void dfs(const int &u, const int &f, const int &l){
  26. dep[u] = dep[f] + ;
  27. dis[u] = dis[f] + l;
  28. fa[u][] = f;
  29. for(int i = ; fa[u][i]; i++)
  30. fa[u][i + ] = fa[fa[u][i]][i];
  31. for(int e = adj[u]; e; e = nxt[e]){
  32. int v = go[e];
  33. if(v == f) continue;
  34. dfs(v, u, len[e]);
  35. }
  36. }
  37.  
  38. inline int lca(int u, int v){
  39. if(dep[u] < dep[v]) swap(u, v);
  40. int delta = dep[u] - dep[v];
  41. for(int i = Log[delta]; i >= ; i--)
  42. if( << i & delta) u = fa[u][i];
  43. if(u == v) return u;
  44. for(int i = Log[dep[u]]; i >= ; i--)
  45. if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
  46. return fa[u][];
  47. }
  48.  
  49. int main(){
  50. scanf("%d%d", &n, &m);
  51. Init_Log();
  52. for(int i = ; i < n; i++){
  53. int x, y, z;
  54. scanf("%d%d%d", &x, &y, &z);
  55. addEdge(x, y, z);
  56. }
  57. dfs(,,);
  58. for(int i = ; i <= m; i++){
  59. int x, y;
  60. scanf("%d%d", &x, &y);
  61. int L = lca(x, y);
  62. cout<<(dis[x] - dis[L]) + (dis[y] - dis[L])<<endl;
  63. }
  64. return ;
  65. }

2.树链剖分

  树链剖分

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

  复杂度O(mlog2 n)

  【code】求两点距离

  1. #include<iostream>
  2. #include<cstdio>
  3. #include<cstdlib>
  4. #include<cstring>
  5. #include<string>
  6. #include<algorithm>
  7. #include<cmath>
  8. using namespace std;
  9.  
  10. const int N = 1e5 + ;
  11. const int oo = 0x3f3f3f3f;
  12.  
  13. int dep[N], sze[N], top[N], son[N], pos[N], idx[N], val[N], fa[N];
  14. int ecnt, adj[N], go[N << ], nxt[N << ], tot, len[N << ];
  15. int n, m, dis[N];
  16.  
  17. inline int Re(){
  18. int i = , f = ; char ch = getchar();
  19. for(; (ch < '' || ch > '') && ch != '-'; ch = getchar());
  20. if(ch == '-') f = -, ch = getchar();
  21. for(; ch >= '' && ch <= ''; ch = getchar())
  22. i = (i << ) + (i << ) + (ch - '');
  23. return i * f;
  24. }
  25.  
  26. inline void Wr(int x){
  27. if(x < ) putchar('-'), x = -x;
  28. if(x > ) Wr(x / );
  29. putchar(x % + '');
  30. }
  31.  
  32. inline void addEdge(const int &u, const int &v, const int &l){
  33. nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v, len[ecnt] = l;
  34. nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u, len[ecnt] = l;
  35. }
  36.  
  37. inline void dfs1(const int &u, const int &f, const int &l){
  38. dep[u] = dep[f] + ;
  39. dis[u] = dis[f] + l;
  40. fa[u] = f;
  41. sze[u] = ;
  42. for(int e = adj[u]; e; e = nxt[e]){
  43. int v = go[e];
  44. if(v == f) continue;
  45. dfs1(v, u, len[e]);
  46. sze[u] += sze[v];
  47. if(sze[v] > sze[son[u]]) son[u] = v;
  48. }
  49. }
  50.  
  51. inline void dfs2(const int &u, const int &f){
  52. if(son[u]){ //先查重儿子, 保证重链连续
  53. top[son[u]] = top[u];
  54. idx[pos[son[u]] = ++tot] = son[u];
  55. dfs2(son[u], u);
  56. }
  57. for(int e = adj[u]; e; e = nxt[e]){
  58. int v = go[e];
  59. if(v == f || v == son[u]) continue;
  60. top[v] = v;
  61. idx[pos[v] = ++tot] = v;
  62. dfs2(v, u);
  63. }
  64. }
  65.  
  66. inline int lca(int u, int v){
  67. while(top[u] != top[v]){
  68. if(dep[top[u]] < dep[top[v]]) swap(u, v);
  69. u = fa[top[u]];
  70. }
  71. if(u == v) return u;
  72. if(dep[u] < dep[v]) return u;
  73. else return v;
  74. }
  75.  
  76. int main(){
  77. // freopen("h.in", "r", stdin);
  78. n = Re(), m = Re();;
  79. for(int i = ; i < n; i++){
  80. int a = Re(), b = Re(), c = Re();
  81. addEdge(a, b, c);
  82. }
  83. dfs1(, , );
  84. pos[] = top[] = idx[] = tot = , dep[] = -;
  85. dfs2(, );
  86. for(int i = ; i <= m; i++){
  87. int a = Re(), b = Re();
  88. int L = lca(a, b);
  89. Wr(dis[a] + dis[b] - * dis[L]), putchar('\n');
  90. }
  91. return ;
  92. }

【离线】

【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

  1. #include<iostream>
  2. #include<cstring>
  3. #include<string>
  4. #include<cstdio>
  5. #include<cstdlib>
  6. #include<algorithm>
  7. #include<vector>
  8. using namespace std;
  9.  
  10. #define mp make_pair
  11. const int N = ;
  12. int ecnt, adj[N], go[N << ], nxt[N << ], len[N << ];
  13. int n, m, qa[N], qb[N], qans[N];
  14. vector<pair<int, int> > vq[N];
  15. int anc[N], dis[N];
  16. bool vst[N];
  17.  
  18. int read(){
  19. int i=,f=;char ch;
  20. for(ch=getchar();(ch<''||ch>'')&&ch!='-';ch=getchar());
  21. if(ch=='-') {f=-;ch=getchar();}
  22. for(;ch>=''&&ch<='';ch=getchar()) i=(i<<)+(i<<)+(ch^);
  23. return f*i;
  24. }
  25.  
  26. inline void wr(int x){
  27. if(x < ) putchar('-'), x = -x;
  28. if(x > ) wr(x / );
  29. putchar(x % + '');
  30. }
  31.  
  32. inline void addEdge(int u, int v, int l){
  33. nxt[++ecnt] = adj[u], adj[u] = ecnt, go[ecnt] = v, len[ecnt] = l;
  34. nxt[++ecnt] = adj[v], adj[v] = ecnt, go[ecnt] = u, len[ecnt] = l;
  35. }
  36.  
  37. inline int getAnc(int u){
  38. return (u == anc[u]) ? u : (anc[u] = getAnc(anc[u]));
  39. }
  40.  
  41. inline void Merge(int u, int v){
  42. int fu = getAnc(u), fv = getAnc(v);
  43. if(fu != fv) anc[fu] = fv;
  44. }
  45.  
  46. inline void tarjan(int u, int f, int d){
  47. dis[u] = dis[f] + d;
  48. for(int i = adj[u]; i; i = nxt[i]){
  49. if(go[i] == f) continue;
  50. if(!vst[go[i]]){
  51. tarjan(go[i], u, len[i]);
  52. Merge(go[i], u);
  53. anc[getAnc(u)] = u;
  54. }
  55. }
  56. vst[u] = true;
  57. for(int i = ; i < vq[u].size(); i++){
  58. if(vst[vq[u][i].first])
  59. qans[vq[u][i].second] = getAnc(vq[u][i].first);
  60. }
  61. }
  62.  
  63. int main(){
  64. n = read(), m = read();
  65. for(int i = ; i < n; i++){
  66. int a, b, c;
  67. a = read(), b = read(), c = read();
  68. addEdge(a, b, c);
  69. }
  70. for(int i = ; i <= m; i++){
  71. int u, v;
  72. u = read(), v = read();
  73. qa[i] = u, qb[i] = v;
  74. vq[u].push_back(mp(v, i)), vq[v].push_back(mp(u, i));
  75. }
  76. for(int i = ; i <= n; i++) anc[i] = i;
  77. tarjan(, , );
  78. for(int i = ; i <= m; i++){
  79. int ans = dis[qa[i]] + dis[qb[i]] - * dis[qans[i]];
  80. wr(ans), putchar('\n');
  81. }
  82. return ;
  83. }

【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. robotframework Selenium2+RFS自动化测试

    支持浏览器版本:Google Chrome (64位) 52.0.2743.82 正式版 52.0.2743.6_chrome_installer 64位 下载地址:http://www.online ...

  2. [D3] Build an Area Chart with D3 v4

    Similar to line charts, area charts are great for displaying temporal data. Whether you’re displayin ...

  3. ES6的学习之路(基础知识总结)

    1. 变量声明 1).let 1)使用let定义的变量不会进行"变量提升" console.log(a);//Uncaught ReferenceError: a is not d ...

  4. 关于js盒子模型的知识梳理

    盒子模型 JS盒子模型中的13个常用属性: clientWidth/clientHeight:可视区域的宽高,宽高+PADDING组成 clientTop/clientLeft:上边框和左边框的宽度 ...

  5. 【 2017 Multi-University Training Contest - Team 9 && hdu 6162】Ch’s gift

    [链接]h在这里写链接 [题意] 给你一棵树,每个节点上都有一个权值. 然后给你m个询问,每个询问(x,y,a,b); 表示询问x->y这条路径上权值在[a,b]范围内的节点的权值和. [题解] ...

  6. Docker基础(一)

    1.安装:安装教程很多,Ubuntu14.04安装比较简单docker[之前使用Ubuntu13.04结果安装了好久也没有安装好,后来就直接是14,04了] 2.docker是容器,那么什么是容器? ...

  7. APK瘦身记,怎样实现高达53%的压缩效果

    作者:非戈@阿里移动安全,很多其它技术干货.请訪问阿里聚安全博客 1.我是怎么思考这件事情的 APK是Android系统安装包的文件格式.关于这个话题事实上是一个老生常谈的题目.不论是公司内部.还是外 ...

  8. CSS布局开篇

    原文: 简书原文:https://www.jianshu.com/p/2c78b927f8c4 开篇 这是我写CSS布局的第一篇文章,之所以将布局从中摘出来单独放一部分是因为我觉得光是布局这块内容就有 ...

  9. bootstrap+fileinput插件实现可预览上传照片功能

    实际项目中运用: 功能:实现上传图片,更改上传图片,移除图片的功能 <!DOCTYPE html> <html> <head> <meta charset=& ...

  10. [Tools] Fix Only Committed Files with Prettier and lint-staged

    In this lesson we'll use prettier and lint-staged to run prettier only on files that have been chang ...