Luogu P3379 最近公共祖先

原题展现

题目描述

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

输入格式

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

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

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

输出格式

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

样例输入 #1

  1. 5 5 4
  2. 3 1
  3. 2 4
  4. 5 1
  5. 1 4
  6. 2 4
  7. 3 2
  8. 3 5
  9. 1 2
  10. 4 5

样例输出 #1

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

提示

对于 \(30\%\) 的数据,\(N\leq 10\),\(M\leq 10\)。

对于 \(70\%\) 的数据,\(N\leq 10000\),\(M\leq 10000\)。

对于 \(100\%\) 的数据,\(N\leq 500000\),\(M\leq 500000\)。

样例说明:

该树结构如下:

第一次询问:\(2, 4\) 的最近公共祖先,故为 \(4\)。

第二次询问:\(3, 2\) 的最近公共祖先,故为 \(4\)。

第三次询问:\(3, 5\) 的最近公共祖先,故为 \(1\)。

第四次询问:\(1, 2\) 的最近公共祖先,故为 \(4\)。

第五次询问:\(4, 5\) 的最近公共祖先,故为 \(4\)。

故输出依次为 \(4, 4, 1, 4, 4\)。

解析

本题是 LCA 的模板

LCA 的做法很多,比如暴力跳,倍增

暴力跳

让深度大的一点不断向上跳,直到两点深度相等

如果两点深度相同但是并不相等,可以两点一起跳

在随机数据下表现优异,因为树会比较平衡,所以近似\(O(\log n)\)

通常会被卡成单次\(O(n)\),其实不难构造,可以构造一个深度大的树(比如链)

本人出的一道题思想类似这样,不过这道题保证了平衡

倍增法

考虑一次跳多一点

记\(fa_{u,k}\)表示距离\(u\)的边数为\(2^k\)的祖先节点则\(fa_{u,k}=fa_{fa_{u,k-1},k-1}\)可以通过dfs求出\(fa\)

如果求LCA,我们可以很快让两点来到相同的深度

考虑求两点深度差,将差二进制拆分,每次跳一个\(2\)的幂,时间复杂度\(O(\log n)\)

当然,没必要真的二进制拆分,因为我们要知道是\(2\)的几次幂,所以用cmathlog2更加方便

这里有一个优化:用\(O(n)\)的时间复杂度递推求出log2的值

然后,如果两点深度相同不相等,有一个自认为巧妙的方法求解

一个性质:如果两点跳到LCA了,继续向上跳依然相等(易证)

如果两点向上跳不相等,那么一定可以继续跳

于是想到一个办法:尝试枚举\(i\)从\(31\)到\(0\),表示尝试跳\(2^i\)

如果向上跳不相同的话,就向上跳,这样,枚举完,LCA就是\(fa_{x,0}\)

核心代码如下,首先是预处理

  1. void dfs(long long x,long long fa)
  2. {
  3. f[x][0]=fa;
  4. dep[x]=dep[fa]+1;
  5. for(int i=1;i<=31;i++)
  6. {
  7. f[x][i]=f[f[x][i-1]][i-1];
  8. }
  9. for(int i=h[x];i;i=a[i].next)
  10. {
  11. if(a[i].to!=fa)
  12. {
  13. dfs(a[i].to,x);
  14. }
  15. }
  16. }

然后是求解

  1. if(dep[x]<dep[y])
  2. {
  3. swap(x,y);
  4. }
  5. while(dep[x] > dep[y])
  6. {
  7. x = f[x][lg[dep[x]-dep[y]] - 1];
  8. }
  9. if(x==y)
  10. {
  11. cout<<x<<endl;
  12. continue;
  13. }
  14. for(int k = lg[dep[x]] - 1; k >= 0;k--)
  15. {
  16. if(f[x][k] != f[y][k])
  17. {
  18. x = f[x][k], y = f[y][k];
  19. }
  20. }

于是,我们得到了一个严格的\(O(\log n)\)算法

Luogu P1967 [NOIP2013 提高组] 货车运输

原题展现

题目描述

A 国有 \(n\) 座城市,编号从 \(1\) 到 \(n\),城市之间有 \(m\) 条双向道路。每一条道路对车辆都有重量限制,简称限重。

现在有 \(q\) 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

输入格式

第一行有两个用一个空格隔开的整数 $ n,m$,表示 \(A\) 国有 $ n$ 座城市和 \(m\) 条道路。

接下来 \(m\) 行每行三个整数 \(x, y, z\),每两个整数之间用一个空格隔开,表示从 $x $ 号城市到 $ y $ 号城市有一条限重为 \(z\) 的道路。

注意: \(x \neq y\),两座城市之间可能有多条道路 。

接下来一行有一个整数 \(q\),表示有 \(q\) 辆货车需要运货。

接下来 \(q\) 行,每行两个整数 \(x,y\),之间用一个空格隔开,表示一辆货车需要从 \(x\) 城市运输货物到 \(y\) 城市,保证 \(x \neq y\)

输出格式

共有 \(q\) 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。

如果货车不能到达目的地,输出 \(-1\)。

样例输入 #1

  1. 4 3
  2. 1 2 4
  3. 2 3 3
  4. 3 1 1
  5. 3
  6. 1 3
  7. 1 4
  8. 1 3

样例输出 #1

  1. 3
  2. -1
  3. 3

提示

对于 \(30\%\) 的数据,\(1 \le n < 1000\),\(1 \le m < 10,000\),\(1\le q< 1000\);

对于 \(60\%\) 的数据,\(1 \le n < 1000\),\(1 \le m < 5\times 10^4\),\(1 \le q< 1000\);

对于 \(100\%\) 的数据,\(1 \le n < 10^4\),\(1 \le m < 5\times 10^4\),$1 \le q< 3\times 10^4 $,\(0 \le z \le 10^5\)。

解析

因为我们想要经过的最小边最大,那么不妨构造一个最大生成树(建议使用克鲁斯卡尔算 法),这样每条边都能尽可能大

然后问题转换为树上查询,同样利用倍增法求\(x->LCA,y->LCA\)路径中的最小边,也是可以预处理的

不过问题不保证树联通,需要判断是否有解

克鲁斯卡尔的优势就体现出来了,我们已经处理了并查集,如果两点祖先不同就直接判断为无解

核心代码如下(码风十分奇怪)

  1. #include<bits/stdc++.h>
  2. #define ll long long
  3. using namespace std;
  4. struct road
  5. {
  6. ll s,t,w;
  7. }r[200005];
  8. struct node
  9. {
  10. ll to,next,w;
  11. }a[200005];
  12. ll n,m,t,k,x,y,fa2[100005],h[100005],fa[100005][33],f[100005][33],dep[100005],lg[100005];
  13. bool cmp(road x,road y)
  14. {
  15. return x.w>y.w;
  16. }
  17. void add(int x,int y,int z)
  18. {
  19. t++;
  20. a[t].to=y;
  21. a[t].w=z;
  22. a[t].next=h[x];
  23. h[x]=t;
  24. }
  25. int find(int x)
  26. {
  27. if(fa2[x]==x)return x;
  28. return fa2[x]=find(fa2[x]);
  29. }
  30. void dfs(long long x,long long fn)
  31. {
  32. fa[x][0]=fn;
  33. dep[x]=dep[fn]+1;
  34. for(int i=1;i<=31;i++)
  35. {
  36. fa[x][i]=fa[fa[x][i-1]][i-1];
  37. f[x][i]=min(f[x][i-1],f[fa[x][i-1]][i-1]);//f数组表示x到fa[x][i]路径的最小值
  38. }
  39. for(int i=h[x];i;i=a[i].next)
  40. {
  41. if(a[i].to!=fn)
  42. {
  43. f[a[i].to][0]=a[i].w;
  44. dfs(a[i].to,x);
  45. }
  46. }
  47. }
  48. int lca(int x,int y)
  49. {
  50. if(dep[x]<dep[y])
  51. {
  52. swap(x,y);
  53. }
  54. while(dep[x] > dep[y])
  55. {
  56. x = fa[x][lg[dep[x]-dep[y]] - 1];
  57. }
  58. if(x==y)
  59. {
  60. return x;
  61. }
  62. for(int k = lg[dep[x]] - 1; k >= 0;k--)
  63. {
  64. if(fa[x][k] != fa[y][k])
  65. {
  66. x = fa[x][k], y = fa[y][k];
  67. }
  68. }
  69. return fa[x][0];
  70. }
  71. int work(int x,int y)//求解x到y路径的最小值,保证y是x祖先
  72. {
  73. ll ans=1e9,deph=dep[x]-dep[y];
  74. while(deph!=0)
  75. {
  76. ll t=lg[deph]-1;
  77. ans=min(ans,f[x][t]);
  78. x=fa[x][t];
  79. deph=dep[x]-dep[y];
  80. }
  81. return ans;
  82. }
  83. int main()
  84. {
  85. cin>>n>>m;
  86. for(int i=1;i<=n;i++)
  87. {
  88. fa2[i]=i;
  89. lg[i] = lg[i-1] + (1 << lg[i-1] == i);
  90. }
  91. for(int i=1;i<=m;i++)
  92. {
  93. cin>>r[i].s>>r[i].t>>r[i].w;
  94. }
  95. sort(r+1,r+m+1,cmp);//克鲁斯卡尔
  96. int k=n-1;
  97. for(int i=1;i<=m;i++)
  98. {
  99. if(k==0)break;
  100. if(find(r[i].s)!=find(r[i].t))
  101. {
  102. add(r[i].s,r[i].t,r[i].w);
  103. add(r[i].t,r[i].s,r[i].w);
  104. fa2[find(r[i].s)]=find(r[i].t);
  105. k--;
  106. }
  107. }
  108. for(int i=1;i<=n;i++)
  109. {
  110. if(find(i)==i)
  111. {
  112. dfs(i,0);
  113. }
  114. }
  115. cin>>k;
  116. for(int i=1;i<=k;i++)
  117. {
  118. cin>>x>>y;
  119. if(find(x)!=find(y))
  120. {
  121. cout<<-1<<endl;
  122. continue;
  123. }
  124. int lcah=lca(x,y);
  125. cout<<min(work(x,lcah),work(y,lcah))<<endl;
  126. }
  127. }

Duck006[DuckOI]Kill the Duck

原题展现

温馨提示

Duck非常不要脸,单推自己的题

后来发现其实有好多一样的题

  • 贪玩的小孩
  • HDU 2586 How far away?

题目描述

XCR是世界名列前茅的OIer,今天在打模拟赛。

他已经AC了前四道题,准备暴切第五题,看着这个题面,突然发现不太对....

他一看五道题的名字

\[\mathtt{\color{red}{X}\color{black}{or}}\\
\mathtt{\color{red}{C}\color{black}{ount\;the\;Number\;of\;Dance\;Schemes}}\\
\mathtt{\color{red}{R}\color{black}{elaxing\;Time }}\\
\mathtt{\color{red}{A}\color{black}{n\; Easy\;Problem}}\\
\mathtt{\color{red}{K}\color{black}{ill\;the\;Duck}}\\
\mathtt{\huge{\color{red}{XCRAK}}}
\]

XCR十分生气,想要杀了DengDuck

DengDuck跑到了一个有\(n\)个结点,\(n-1\)条边的树上

这个树的每个边都是无向的,都有边权

XCR现在有\(m\)次询问,第\(i(1 \leq i \leq m)\)次给出两个正整数\(x_i\)和\(y_i\),含义如下

DengDuck 在点 \(x_i(1 \leq x_i \leq n)\) 上,XCR在点 \(y_i(1 \leq y_i \leq n)\) 上

对于每次询问,请问XCR离DengDuck的距离是多少?

输入格式

第一行一个整数\(n\)

接下来\(n-1\)行每行三个正整数分别表示一条边的起点,终点,边权

第\(n+1\)行一个正整数\(m\)

接下来\(m\)行每行两个正整数\(x_i\)和\(y_i\)

输出格式

有\(m\)行,每行一个正整数,表示DengDuck和XCR的距离

样例输入 #1

  1. 3
  2. 1 2 3
  3. 2 3 4
  4. 2
  5. 1 2
  6. 1 3

样例输出 #1

  1. 3
  2. 7

样例输入 #2

  1. 3
  2. 1 3 10
  3. 1 2 13
  4. 5
  5. 1 1
  6. 2 2
  7. 3 1
  8. 2 1
  9. 1 3

样例输出 #2

  1. 0
  2. 0
  3. 10
  4. 13
  5. 10

样例输入 #3

  1. 14
  2. 5 7 12
  3. 7 11 15
  4. 5 14 12
  5. 14 3 17
  6. 7 1 19
  7. 14 4 14
  8. 1 12 16
  9. 1 6 16
  10. 12 9 19
  11. 9 10 10
  12. 7 2 11
  13. 4 8 10
  14. 2 13 14
  15. 17
  16. 6 11
  17. 14 14
  18. 13 11
  19. 6 10
  20. 12 6
  21. 8 7
  22. 9 9
  23. 10 11
  24. 13 10
  25. 1 4
  26. 2 12
  27. 13 4
  28. 2 7
  29. 2 1
  30. 12 2
  31. 10 11
  32. 4 7

样例输出 #3

  1. 50
  2. 0
  3. 40
  4. 61
  5. 32
  6. 48
  7. 0
  8. 79
  9. 89
  10. 57
  11. 46
  12. 63
  13. 11
  14. 30
  15. 46
  16. 79
  17. 38

提示

对于一定的数据 \(n,m\)的范围 特殊限制
前\(5\%\)的数据 \(1~20\)
前\(20\%\)的数据 \(1~3000\)
另外的\(5\%\)的数据 \(1~3000\) \(m=1\)
所有数据 \(1~100000\)

解析

预处理出\(dis_i\)表示点\(i\)到根\(1\)的距离,答案是\(dis_x+dis_y-2dis_{lca(x,y)}\)

非常容易证明

代码如下

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. int n, k, b[1000005], x, y, z, tot, h[500005], len[500005], fa[500005][33], dep[500005], lg[500005],
  4. f[1000005], ans;
  5. struct node {
  6. int to, next, w;
  7. } a[1000005];
  8. void dfs(long long x, long long fn, long long l) {
  9. fa[x][0] = fn;
  10. dep[x] = dep[fn] + 1;
  11. len[x] = l;
  12. for (int i = 1; i <= 31; i++) {
  13. fa[x][i] = fa[fa[x][i - 1]][i - 1];
  14. }
  15. for (int i = h[x]; i; i = a[i].next) {
  16. if (a[i].to != fn) {
  17. dfs(a[i].to, x, l + a[i].w);
  18. }
  19. }
  20. }
  21. int lca(int x, int y) {
  22. if (dep[x] < dep[y]) {
  23. swap(x, y);
  24. }
  25. while (dep[x] > dep[y]) {
  26. x = fa[x][lg[dep[x] - dep[y]] - 1];
  27. }
  28. if (x == y) {
  29. return x;
  30. }
  31. for (int k = lg[dep[x]] - 1; k >= 0; k--) {
  32. if (fa[x][k] != fa[y][k]) {
  33. x = fa[x][k], y = fa[y][k];
  34. }
  35. }
  36. return fa[x][0];
  37. }
  38. void add(int x, int y, int z) {
  39. ++tot;
  40. a[tot].to = y;
  41. a[tot].next = h[x];
  42. a[tot].w = z;
  43. h[x] = tot;
  44. }
  45. void answer(int x, int fn) {
  46. for (int i = h[x]; i; i = a[i].next) {
  47. if (a[i].to != fn) {
  48. answer(a[i].to, x);
  49. f[x] += f[a[i].to];
  50. }
  51. }
  52. ans = max(ans, f[x]);
  53. }
  54. int main() {
  55. cin >> n;
  56. for (int i = 1; i <= n; ++i) {
  57. lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
  58. }
  59. for (int i = 1; i <= n - 1; i++) {
  60. cin >> x >> y >> z;
  61. add(x, y, z);
  62. add(y, x, z);
  63. }
  64. dfs(1, 0, 0);
  65. cin >> k;
  66. for (int i = 1; i <= k; i++) {
  67. cin >> x >> y;
  68. int t = lca(x, y);
  69. cout << len[x] + len[y] - 2 * len[t] << endl;
  70. }
  71. }

浅谈倍增法求解LCA的更多相关文章

  1. HDU 2586 倍增法求lca

    How far away ? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)To ...

  2. 倍增法求LCA

    倍增法求LCA LCA(Least Common Ancestors)的意思是最近公共祖先,即在一棵树中,找出两节点最近的公共祖先. 倍增法是通过一个数组来实现直接找到一个节点的某个祖先,这样我们就可 ...

  3. 倍增法求lca(最近公共祖先)

    倍增法求lca(最近公共祖先) 基本上每篇博客都会有参考文章,一是弥补不足,二是这本身也是我学习过程中找到的觉得好的资料 思路: 大致上算法的思路是这样发展来的. 想到求两个结点的最小公共祖先,我们可 ...

  4. LCA—倍增法求解

    LCA(Least Common Ancestors) 即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先. 常见解法一般有三种 这里讲解一种在线算法-倍增 首先我们定义fa[u][j ...

  5. 【数据结构】浅谈倍增求LCA

    思路 运用树上倍增法可以高效率地求出两点x,y的公共祖先LCA 我们设f[x][k]表示x的2k辈祖先 f[x][0]为x的父节点 因为从x向根节点走2k 可以看成从x走2k-1步 再走2k-1步 所 ...

  6. 树上倍增法求LCA

    我们找的是任意两个结点的最近公共祖先, 那么我们可以考虑这么两种种情况: 1.两结点的深度相同. 2.两结点深度不同. 第一步都要转化为情况1,这种可处理的情况. 先不考虑其他, 我们思考这么一个问题 ...

  7. 倍增法求LCA(最近公共最先)

    对于有根树T的两个结点u.v,最近公共祖先x=LCA(u,v)表示一个结点x,满足x是u.v的祖先且x的深度尽可能大. 如图,根据定义可以看出14和15的最近公共祖先是10,   15和16的最近公共 ...

  8. 浅谈倍增LCA

    题目链接:https://www.luogu.org/problemnew/show/P3379 刚学了LCA,写篇blog加强理解. LCA(Least Common Ancestors),即最近公 ...

  9. 在线倍增法求LCA专题

    1.cojs 186. [USACO Oct08] 牧场旅行 ★★   输入文件:pwalk.in   输出文件:pwalk.out   简单对比时间限制:1 s   内存限制:128 MB n个被自 ...

随机推荐

  1. 剖析虚幻渲染体系(14)- 延展篇:现代渲染引擎演变史Part 1(萌芽期)

    目录 14.1 本篇概述 14.1.1 游戏引擎简介 14.1.2 游戏引擎模块 14.1.3 游戏引擎列表 14.1.3.1 Unreal Engine 14.1.3.2 Unity 14.1.3. ...

  2. 【UWP】实现一个波浪进度条

    好久没写 blog 了,一个是忙,另外一个是觉得没啥好写.废话不多说,直接上效果图: 可能看到这波浪线你觉得会很难,但看完这篇 blog 后应该你也会像我一样恍然大悟.图上的图形,我们可以考虑是由 3 ...

  3. 如何得到个性化banner

    介绍 有时候用一些脚本工具,会有一些由其他字符组成的字符.(如下面这个我还在写的) 使用 kali自带了这个工具 -- figlet. figlet AuToIP 就可以得到上面的字符啦! 另外如果想 ...

  4. FSB—QPI—DMI总线的发展

    intel CPU有的是前端总线(FSB),有的是QPI总线,有的又是DMI总线 FSB总线(由于cpu的发展,fsb总线制约了cpu的发展,所以该总线已经渐渐淡出历史舞台) FSB即Front Si ...

  5. MySQL存储引擎、基础数据类型、约束条件

    MySQL存储引擎 存储引擎 # 存储引擎可以堪称是处理数据的不同方式 # 查看存储引擎的方式 show engines; # 需要掌握的四个存储引擎 MyISAM MySQL5.5之前的默认的存储引 ...

  6. vulnhub DC:1渗透笔记

    DC:1渗透笔记 靶机下载地址:https://www.vulnhub.com/entry/dc-1,292/ kali ip地址 信息收集 首先扫描一下靶机ip地址 nmap -sP 192.168 ...

  7. vue2响应式原理与vue3响应式原理对比

    VUE2.0 核心 对象:通过Object.defineProtytype()对对象的已有属性值的读取和修改进行劫持 数组:通过重写数组更新数组一系列更新元素的方法来实现元素的修改的劫持 Object ...

  8. Asp.Net在线预览Word文档的解决方案与思路

    前几天有个老项目找到我,有多老呢?比我工作年限都长,见到这个项目我还得叫一声前辈. 这个项目目前使用非常稳定,十多年了没怎么更新过,现在客户想加一个小功能:在线预览Word文档. 首先想到的是用第三方 ...

  9. 浅谈 TCP、IP、DNS 和 HTTP 的关系

    一.浅谈三个协议的基本概念 1.IP 协议 按层次分,IP网际协议位于网络层,几乎所有的网络的系统都会用到 IP 协议,其重要性非同一般.IP 协议作用就是把各种数据包传送给对方,对方的地址就要看其 ...

  10. 神经网络 CNN 名词解释

    隐藏层 不是输入或输出层的所有层都称为隐藏层. 激活和池化都没有权重 使层与操作区分开的原因在于层具有权重.由于池操作和激活功能没有权重,因此我们将它们称为操作,并将其视为已添加到层操作集合中. 例如 ...