hihocoder1069最近公共祖先·三(LCA在线算法--DFS+RMQ-ST)

树上任意两点的最近祖先,必定就是这两个节点的最短路径上深度最小的那个点。

例如:下图中,节点7和5,其最短路径为7--4--1--5, 这条路径上深度最小的点为节点1,其深度为1.节点1即为节点7和5的LCA。

因此,要找到任意两个节点的LCA,只需要先找到上述最短路径,再找到最短路径中深度最小的点。而这下面所述LCA在线算法所做的事。

LCA在线算法描述(以上图为例):

1.获得“最短路径”(并不是真正的一条路径,包含其他节点,但不影响算法的正确性)

采用DFS遍历整棵树,得到以下数据:

(1)遍历序列p:0  1  3  1  4  7  4  8  4  1  5  1  0  2  6  2  0

(2)各节点的深度序列        depth: 0  1   1   2   2  2   2    3  3

(3)各节点在序列p中首次出现的位置序列pos: 0  1  13  2  4  10  14  5  7

有了以上数据,假设现在我们要求节点7和5的最短路径,我们可以这样做:

(1)首先,从pos序列中获得节点7和节点5在p序列中第一次出现的位置分别为:pos[7] = 5, pos[5] = 10;

(2)得到p序列中[5, 10]这一段子序列s:7  4  8  4  1  5

(3)s序列中深度最小的点即节点1就是我们要找的节点7和节点5的LCA。

注意到,此时的s序列并非是从节点7到节点5的一条最短路径,它除了包含7到5的最短路径上的节点外,还包含了一些其他的节点,但这些其他的节点都是以节点1为根的子树上的节点,他们的深度都比节点1大,不影响我们算法对正确结果的求解。

2.如何快速的获得一段序列中深度最小的节点

求解区间最值的问题是我们所熟悉的经典的RMQ问题,用RMQ-ST算法即可。由于RMQ-ST算法是在线的,故我们的LCA算法也是在线的。

下面是我的代码实现:

 1 #include <iostream>
 2 #include <string>
 3 #include <map>
 4 #include <vector>
 5 #include <algorithm>
 6 #include <cmath>
 7
 8 using namespace std;
 9
10 #define MAXN 100005
11
12 map<string, int> mp;
13 string name[2*MAXN];
14 vector<int> v[2*MAXN];
15 int p[4*MAXN], depth[2*MAXN], pos[2*MAXN];
16 int pre_cal[4*MAXN][20];
17 int cnt, n, m;
18
19 void dfs(int i, int d)
20 {
21     pos[i] = cnt;
22     p[cnt++] = i;
23     depth[i] = d;
24     if(v[i].empty()) return;
25     for(int j=0; j<v[i].size(); ++j)
26     {
27         dfs(v[i][j], d+1);
28         p[cnt++] = i;
29     }
30 }
31
32 void rmq()
33 {
34     for(int i=0; i<4*MAXN; ++i) pre_cal[i][0] = i;
35     for(int j=1; (1<<(j-1))<4*MAXN; ++j)
36         for(int i=0; i+(1<<(j-1))<4*MAXN; ++i)
37                 pre_cal[i][j] = depth[p[pre_cal[i][j-1]]]<depth[p[pre_cal[i+(1<<(j-1))][j-1]]]?pre_cal[i][j-1]:pre_cal[i+(1<<(j-1))][j-1];
38 }
39
40 string lca(int a, int b)
41 {
42     int k = floor(log(b-a+1)/log(2));
43     int x = pre_cal[a][k], y = pre_cal[b-(1<<k)+1][k];
44     return depth[p[x]]<depth[p[y]]?name[p[x]]:name[p[y]];
45 }
46
47 void init()
48 {
49     cnt = 0;
50     mp.clear();
51     for(int i=0; i<2*MAXN; ++i) v[i].clear();
52 }
53
54 int main()
55 {
56     string name1, name2;
57     while(cin>>n)
58     {
59         init();
60         while(n--)
61         {
62             cin>>name1>>name2;
63             if(mp.find(name1)==mp.end())
64             {
65                 mp[name1] = cnt;
66                 name[cnt++] = name1;
67             }
68             if(mp.find(name2)==mp.end())
69             {
70                 mp[name2] = cnt;
71                 name[cnt++] = name2;
72             }
73             v[mp[name1]].push_back(mp[name2]);
74         }
75         cnt = 0;
76         dfs(0, 0);
77         rmq();
78         cin>>m;
79         while(m--)
80         {
81             cin>>name1>>name2;
82             int a = mp[name1], b = mp[name2];
83             cout<<(pos[a]<pos[b]?lca(pos[a], pos[b]):lca(pos[b], pos[a]))<<endl;
84         }
85     }
86
87     return 0;
88 }

题目链接:http://hihocoder.com/problemset/problem/1069

时间: 2024-11-23 11:33:56

hihocoder1069最近公共祖先·三(LCA在线算法--DFS+RMQ-ST)的相关文章

P3379 【模板】最近公共祖先(LCA)(dfs序)

P3379 [模板]最近公共祖先(LCA) 题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每行包含两个正整数x.y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树). 接下来M行每行包含两个正整数a.b,表示询问a结点和b结点的最近公共祖先. 输出格式: 输出包含M行,每行包含一个正整数,依次为每一个询问的结果. 输入输出样例 输入

hihoCoder week17 最近公共祖先&#183;三 lca st表

记录dfs序列,dfn[tot] 记录第tot次访问的节点 然后查两点在dfs序中出现的第一次 id[u] id[v] 然后  找 dep[k] = min( dep[i] ) {i 属于 [id[u], id[v]]} 最后dfn[k] 就是所求.. 感觉弄来弄去 就是 在映射... 无非就是 求一段序列深度最小的节点编号 #include <bits/stdc++.h> using namespace std; const int N = 2e5+10; int n, cnt, tot,

最近公共祖先(LCA)---tarjan算法

LCA(最近公共祖先).....可惜我只会用tarjan去做 真心感觉tarjan算法要比倍增算法要好理解的多,可能是我脑子笨吧略略略 最近公共祖先概念:在一棵无环的树上寻找两个点在这棵树上深度最大的公共的祖先节点,也就是离这两个点最近的祖先节点. 最近公共祖先的应用:求解两个有且仅有一条确定的最短路径的路径 举个例子吧,如下图所示4和5的最近公共祖先是2,5和3的最近公共祖先是1,2和1的最近公共祖先是1. 这就是最近公共祖先的基本概念了,那么我们该如何去求这个最近公共祖先呢? Tarjan介

hihoCoder_#1069_最近公共祖先&#183;三(RMQ-ST模板)

#1069 : 最近公共祖先·三 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 上上回说到,小Hi和小Ho使用了Tarjan算法来优化了他们的"最近公共祖先"网站,但是很快这样一个离线算法就出现了问题:如果只有一个人提出了询问,那么小Hi和小Ho很难决定到底是针对这个询问就直接进行计算还是等待一定数量的询问一起计算.毕竟无论是一个询问还是很多个询问,使用离线算法都是只需要做一次深度优先搜索就可以了的. 那么问题就来了,如果每次计算都只针对一个询问进行的话

最近公共祖先(lca)

囧啊囧. lca的求法太多了 倍增,tarjan,st,lct,hld.... 后边三个我就不写了,其中st我没写过,估计用不上,在线用倍增,离线用tarjan就行了. 嗯. 第一种,倍增(nlogn,在线): 倍增的思想用在树上,即可以求出lca. 我们维护二维数组,f[i][j],表示i号点的第2^j号祖先,显然2^0=1也就是f[i][0]就是他的父亲 我们需要用dfs维护一个深度数组(求lca需要用) 还需要倍增求出所有的f[i][j],学过st的都应该知道,在这里f[i][j]=f[

洛谷P3379 【模板】最近公共祖先(LCA)

P3379 [模板]最近公共祖先(LCA) 152通过 532提交 题目提供者HansBug 标签 难度普及+/提高 提交  讨论  题解 最新讨论 为什么还是超时.... 倍增怎么70!!题解好像有倍- 题面这个地方写错了 无论是用RMQ+dfs还是tarjan- 为什么我的倍增超时了 求助!为什么只有70分 题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接

【原创】洛谷 LUOGU P3379 【模板】最近公共祖先(LCA) -&gt; 倍增

P3379 [模板]最近公共祖先(LCA) 题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每行包含两个正整数x.y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树). 接下来M行每行包含两个正整数a.b,表示询问a结点和b结点的最近公共祖先. 输出格式: 输出包含M行,每行包含一个正整数,依次为每一个询问的结果. 输入输出样例 输入

洛谷 P3379 【模板】最近公共祖先(LCA) 如题

P3379 [模板]最近公共祖先(LCA) 时空限制1s / 512MB 题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每行包含两个正整数x.y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树). 接下来M行每行包含两个正整数a.b,表示询问a结点和b结点的最近公共祖先. 输出格式: 输出包含M行,每行包含一个正整数,依次为每一个询

luogo p3379 【模板】最近公共祖先(LCA)

[模板]最近公共祖先(LCA) 题意 给一个树,然后多次询问(a,b)的LCA 模板(主要参考一些大佬的模板) #include<bits/stdc++.h> //自己的2点:树的邻接链表(静态)表示; lca 的倍增算法 //优化 log[] const int maxn=500010; int N,M,S;//S根节点标号 int head[maxn];//head[i]=k 以i为起点的第一条边是edge[k] int dep[maxn],dp[maxn][21];//dp[i][j]