LCA的两种求法

HDU 2586

题意:一棵树,多次询问任意两点的路径长度。

LCA:最近公共祖先Least Common Ancestors。两个节点向根爬,第一个碰在一起的结点。

求出x, y的最近公共祖先lca后,假设dist[x]为x到根的距离,那么x->y的距离为dist[x]+dist[y]-2*dist[lca]

求最近公共祖先解法常见的有两种

1, tarjan+并查集

2,树上倍增

首先是树上倍增。

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <iostream>
 4 #include <algorithm>
 5
 6 using namespace std;
 7 const int maxn = 4e4 + 7;
 8
 9 int T, n, m, u, v, w;
10
11 int first[maxn], sign, st[maxn][21], level[maxn], dist[maxn];
12
13 struct Node {
14     int to, w, next;
15 } edge[maxn * 2];
16
17 inline void init() {
18     for(int i = 0; i <= n; i ++ ) {
19         first[i] = -1;
20     }
21     sign = 0;
22 }
23
24 inline void add_edge(int u, int v, int w) {
25     edge[sign].to = v;
26     edge[sign].w = w;
27     edge[sign].next = first[u];
28     first[u] = sign ++;
29 }
30
31 void dfs(int now, int father) {
32     level[now] = level[father] + 1;
33     st[now][0] = father;
34     for(int i = 1; (1 << i) <= level[now]; i ++ ) {
35         st[now][i] = st[ st[now][i - 1] ][i - 1];
36     }
37     for(int i = first[now]; ~i; i = edge[i].next) {
38         int to = edge[i].to, w = edge[i].w;
39         if(to == father) {
40             continue;
41         }
42         dist[to] = dist[now] + w;
43         dfs(to, now);
44     }
45 }
46
47 int LCA(int x, int y) {
48     if(level[x] > level[y]) {
49         swap(x, y);
50     }
51     for(int i = 20; i >= 0; i -- ) {
52         if(level[x] + (1 << i) <= level[y]) {
53             y = st[y][i];
54         }
55     }
56     if(x == y) {
57         return x;
58     }
59     for(int i = 20; i >= 0; i -- ) {
60         if(st[x][i] == st[y][i]) {
61             continue;
62         } else {
63             x = st[x][i], y = st[y][i];
64         }
65     }
66     return st[x][0];
67 }
68
69 int main() {
70     scanf("%d", &T);
71     while(T--) {
72         scanf("%d %d", &n, &m);
73         init();
74         for(int i = 1; i <= n - 1; i ++ ) {
75             scanf("%d %d %d", &u, &v, &w);
76             add_edge(u, v, w);
77             add_edge(v, u, w);
78         }
79         memset(level, 0, sizeof(level));
80         memset(st, 0, sizeof(st));
81         dist[1] = 0;
82         dfs(1, 0);
83         for(int i = 1; i <= m; i ++ ) {
84             int u, v;
85             scanf("%d %d", &u, &v);
86             int lca = LCA(u, v);
87             printf("%d\n", dist[u] + dist[v] - 2 * dist[lca]);
88         }
89     }
90
91     return 0;
92 }

然后是tarjan的解法(参考算法竞赛进阶指南)

个人理解,tarjan算法就是对搜索的过程中维护了一些性质。在搜索的过程中把点分为三类

1,已经范围且回溯的点,代码中vis[x]=2

2,已经访问还没有回溯的点,代码中vis[x]=1

3,未被标记的点

每一个回溯的点都用并查集连到他的父节点,这样如果我们x点正在访问,我们需要知道x,y的lca,而y已经被访问了,那么并查集y的根就是x,y的lca

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <iostream>
  4 #include <algorithm>
  5
  6 using namespace std;
  7 const int MAXN = 5e4 + 7;
  8 const int INF = 0x3f3f3f3f;
  9
 10 int n, m, first[MAXN], sign;
 11
 12 int pre[MAXN], ans[MAXN], vis[MAXN], dist[MAXN], lca[MAXN], indexs;
 13
 14 vector<pair<int, int> >query[MAXN]; ///y, id
 15
 16 struct Node {
 17     int to, w, next;
 18 } edge[MAXN * 2];
 19
 20 inline void init() {
 21     for(int i = 0; i <= n; i++ ) {
 22         first[i] = -1;
 23         pre[i] = i;
 24         query[i].clear();
 25         ans[i] = INF;
 26         vis[i] = 0;
 27     }
 28     sign = 0;
 29 }
 30
 31 inline void add_edge(int u, int v, int w) {
 32     edge[sign].to = v;
 33     edge[sign].w = w;
 34     edge[sign].next = first[u];
 35     first[u] = sign++;
 36 }
 37
 38 int findx(int x) {
 39     return pre[x] == x ? x : pre[x] = findx(pre[x]);
 40 }
 41
 42 inline void join(int x, int y) {
 43     int fx = findx(x), fy = findx(y);
 44     pre[fx] = fy;
 45 }
 46
 47 inline bool same(int x, int y) {
 48     return findx(x) == findx(y);
 49 }
 50
 51 void tarjan(int now) {
 52     vis[now] = 1;
 53     for(int i = first[now]; ~i; i = edge[i].next) {
 54         int to = edge[i].to;
 55         if(!vis[to]) {
 56             dist[to] = dist[now] + edge[i].w;
 57             tarjan(to);
 58             pre[to] = now;
 59         }
 60     }
 61     for(int i = 0; i < query[now].size(); i++ ) {
 62         int y = query[now][i].first, id = query[now][i].second;
 63         if(vis[y] == 2) {
 64             int lca = findx(y);
 65             ans[id] = min(ans[id], dist[now] + dist[y] - 2 * dist[lca]);
 66         }
 67     }
 68     vis[now] = 2;
 69 }
 70
 71 int main()
 72 {
 73     int T;
 74     scanf("%d", &T);
 75     while(T--) {
 76         scanf("%d %d", &n, &m);
 77         init();
 78         for(int i = 1; i <= n - 1; i++ ) {
 79             int u, v, w;
 80             scanf("%d %d %d", &u, &v, &w);
 81             add_edge(u, v, w);
 82             add_edge(v, u, w);
 83         }
 84         for(int i = 1; i <= m; i++ ) {
 85             int x, y;
 86             scanf("%d %d", &x, &y);
 87             if(x == y) {
 88                 ans[i] = 0;
 89                 continue;
 90             }
 91             query[x].push_back(make_pair(y, i));
 92             query[y].push_back(make_pair(x, i));
 93             ans[i] = INF;
 94         }
 95         tarjan(1);
 96         for(int i = 1; i <= m; i++ ) {
 97             printf("%d\n", ans[i]);
 98         }
 99     }
100     return 0;
101 }

原文地址:https://www.cnblogs.com/Q1143316492/p/9194214.html

时间: 2024-11-10 18:59:36

LCA的两种求法的相关文章

【总结】LCA的4种求法

前言 LCA的求法有多重多样,总结下来是下面这4种.希望大家可以加油! 暴力求LCA 我们考虑dfs求出每一个点的父亲(在当前根下),然后直接先暴力跳到同一个深度,再同时跳 void dfs(int u,int f){ fa[u]=f;dep[u]=dep[f]+1; for(re int i=front[u];i;i=e[i].nxt){ int v=e[i].to; if(v==f)continue; dfs(v,u); } } int lca(int u,int v){ if(dep[u]

LCA的两种写法

第一种是离线的Tarjan算法 #include<cstdio> using namespace std; int rd(){ int x=0,fl=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-'){fl=-1;}ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} re

逆序对的两种求法(复习)

逆序对 对于一个数列\(a_1...a_n\),定义一有序对\((i,j)\)当且仅当\(i<j\)且\(a_i>a_j\)为逆序对.接着我们来考虑怎么求 *1. 归并排序 回顾归并排序的过程,将当且的数列\([l,r]\)分成两个长度相等的部分\([l,mid]\)和\([mid+1,r]\),分治下去排序,每次合并的代价是区间的长度,所以得到时间复杂度为: \[ T(n)=2T(\frac{n}{2})+O(n) \] 根据\(master\)定理可知时间复杂度为\(\Theta(nlog

【模板】lca的几种求法

1,倍增 vector<int>p[maxn]; int dep[maxn],f[maxn][20];//f[i][0]保存i的父亲 inline void dfs(int u,int fa,int d) { dep[u]=d;f[u][0]=fa; for(int i=0;i<p[u].size();i++) { int v=p[u][i]; if(v==fa)continue; dfs(v,u,d+1); } } inline void init(int n) { for(int j

重连通量的邻接矩阵和邻接表两种形式的求法

邻接矩阵: #include <cstdio> #include <cstring> #include <stack> using namespace std; #define min(a,b) a<b?a:b #define N 105 int dfn[N],low[N],mat[N][N],visit[N],tmpdfn,n; struct Edge{ int x,y; void print(){ printf("%d-%d\n",x,y)

Count on a tree SPOJ 主席树+LCA(树链剖分实现)(两种存图方式)

Count on a tree SPOJ 主席树+LCA(树链剖分实现)(两种存图方式) 题外话,这是我第40篇随笔,纪念一下.<( ̄︶ ̄)[GO!] 题意 是说有棵树,每个节点上都有一个值,然后让你求从一个节点到另一个节点的最短路上第k小的值是多少. 解题思路 看到这个题一想以为是树链剖分+主席树,后来写着写着发现不对,因为树链剖分我们分成了一小段一小段,这些小段不能合并起来求第k小,所以这个想法不对.奈何不会做,查了查题解,需要用LCA(最近公共祖先),然后根据主席树具有区间加减的性质,我们

hdu1559,1081最大子矩阵和的两种题型

最大子矩阵是一种典型的dp问题.某种程度上说是最大连续子序列和问题的扩展. 1081 原题地址 这是最常见的最大子矩阵问题的体型.简单的解决方案就是把列累加,遍历任意两行的累加值的差值,然后就转换成了普通的最大连续子序列和问题.从而将二维问题转换为一维.时间复杂度较高为O(N^3) 代码: #include<iostream> #include<cstdio> #include<cstring> using namespace std; const int MAX=10

图的两种存储(邻接矩阵和邻接表)和两种遍历(DFS和BFS)

图的表示有很多,形式不固定,我暂时先记录我已经懂了的,能写的两种即大多数人应该都知道的邻接矩阵和邻接表. 邻接矩阵: 这里的邻接矩阵和离散数学说的有一点不同,至少有向图的邻接矩阵不同(离散书上的有向图的邻接矩阵求法到是有点像求任意两点的最短路径的Floyd算法) 以上都是(我现有知识认为的)废话: 重点 : G : 表示图: Nv:表示图的点数: Ne:表示图的边数: 邻接矩阵 即是一个 Nv * Nv 的矩阵,矩阵是用来储存  权值的(如果是带权图且有边的话),如果是无权图的的话,如果两顶点有

[BJWC2018]Border 的四种求法(后缀自动机+链分治+线段树合并)

题目描述 给一个小写字母字符串 S ,q 次询问每次给出 l,r ,求 s[l..r] 的 Border . Border: 对于给定的串 s ,最大的 i 使得 s[1..i] = s[|s|-i+1..|s|], |s| 为 s 的长度. 题解 这题的描述很短,给人一种很可做的假象. 暴力1:每次对区间lr做一次KMP,求出border数组,复杂度nq. 暴力2:构建后缀自动机,用线段树合并维护出right集合考虑到两个串的最长后缀为他们在parent树上的LCA的len,所以我们可以在pa