最近公共祖先-三(RMQ-ST)
描述
上上回说到,小Hi和小Ho使用了Tarjan算法来优化了他们的“最近公共祖先”网站,但是很快这样一个离线算法就出现了问题:如果只有一个人提出了询问,那么小Hi和小Ho很难决定到底是针对这个询问就直接进行计算还是等待一定数量的询问一起计算。毕竟无论是一个询问还是很多个询问,使用离线算法都是只需要做一次深度优先搜索就可以了的。
那么问题就来了,如果每次计算都只针对一个询问进行的话,那么这样的算法事实上还不如使用最开始的朴素算法呢!但是如果每次要等上很多人一起的话,因为说不准什么时候才能够凑够人——所以事实上有可能要等上很久很久才能够进行一次计算,实际上也是很慢的!
“那到底要怎么办呢?在等到10分钟,或者凑够一定数量的人两个条件满足一个时就进行运算?”小Ho想出了一个折衷的办法。
“哪有这么麻烦!别忘了和离线算法相对应的可是有一个叫做在线算法的东西呢!”小Hi笑道。
小Ho面临的问题还是和之前一样:假设现在小Ho现在知道了N对父子关系——父亲和儿子的名字,并且这N对父子关系中涉及的所有人都拥有一个共同的祖先(这个祖先出现在这N对父子关系中),他需要对于小Hi的若干次提问——每次提问为两个人的名字(这两个人的名字在之前的父子关系中出现过),告诉小Hi这两个人的所有共同祖先中辈分最低的一个是谁?
提示:最近公共祖先无非就是两点连通路径上高度最小的点嘛!×Close
提示:最近公共祖先无非就是两点连通路径上高度最小的点嘛!
“那你快教我啊!”小Ho耐不住性子。
“不要急,且听我缓缓道来,还记得很久之前我和你说过的最近公共祖先其实就是这两个点连通路径上的那个折点么(参见hiho一下第十一周树的直径)”小Hi问道。
“记得!”
“这个折点也就是这2点所连路径上深度最小的那个点了!那么这个问题其实和我们之前所提到的那个求区间最小值的是不是差不多(参见hiho一下第十六周——RMQ-ST算法),只不过一个是在数组上的区间,一个是在树上的区间?”小Hi问道。
“你非要这么说那我只能说是啦。。但是树和数组还是差了挺远的吧。”小Ho表示汗颜。
小Hi点了点头,随即道:“那就这么弄一下,我从树的根节点开始进行深度优先搜索,每次经过某一个点——无论是从它的父亲节点进入这个点,还是从它的儿子节点返回这个点,都按顺序记录下来。这样,是不是就把一棵树转换成了一个数组?而找到树上两个节点的最近公共祖先,无非就是找到这两个节点最后一次出现在数组中的位置所囊括的一段区间中深度最小的那个点?”
小Ho显然是没有料到小Hi还有这一招,一上来也是感觉明显就不对嘛,毕竟好好的树怎么随便就弄成数组了不是,但是静下心来仔细想想:“从第一个点离开(返回它的父亲节点),到从第二个点离开(返回它的父亲节点)的这一段路程,的确经过的深度最小的点就是‘最近公共祖先’这一个点!”
看着小Ho露出了惊讶的神情,小Hi满意的点了点头,道:“这就是一个很好的将树转换成数组来进行某些特殊算法的方法!而且你仔细看看就会发现转换出的数组的长度其实就是边数的2倍而已,也是O(n)的级别呢~”
“原来是这样!那这次我只需要简单的套用之前写的算法,很简单嘛!”小Ho笑道。
“那是自然,你也不看看之前我们积累了一个月呢,现在你要是还磨磨蹭蹭的,回国怎么向河蟹先生交代!”
“嘿嘿嘿……”
Close
输入
每个测试点(输入文件)有且仅有一组测试数据。
每组测试数据的第1行为一个整数N,意义如前文所述。
每组测试数据的第2~N+1行,每行分别描述一对父子关系,其中第i+1行为两个由大小写字母组成的字符串Father_i, Son_i,分别表示父亲的名字和儿子的名字。
每组测试数据的第N+2行为一个整数M,表示小Hi总共询问的次数。
每组测试数据的第N+3~N+M+2行,每行分别描述一个询问,其中第N+i+2行为两个由大小写字母组成的字符串Name1_i, Name2_i,分别表示小Hi询问中的两个名字。
对于100%的数据,满足N<=10^5,M<=10^5, 且数据中所有涉及的人物中不存在两个名字相同的人(即姓名唯一的确定了一个人),所有询问中出现过的名字均在之前所描述的N对父子关系中出现过,且每个输入文件中第一个出现的名字所确定的人是其他所有人的公共祖先。
输出
对于每组测试数据,对于每个小Hi的询问,按照在输入中出现的顺序,各输出一行,表示查询的结果:他们的所有共同祖先中辈分最低的一个人的名字。
Sample Input
4
Adam Sam
Sam Joey
Sam Micheal
Adam Kevin
3
Sam Sam
Adam Sam
Micheal Kevin
Sample Output
Sam
Adam
Adam
题解
对于这个题,它的核心部分其实就是通过找到每一个区间的深度最小值,然后把它的编号(第几次出现)写入dp数组。
当在LCA函数里面调用query函数的时候,因为之前已经通过RMQ-ST求得了 所有已知的区间的深度最小值所对应的编号(第几次出现),所以query函数就可以找到dp数组里面存的深度最小值对应的编号(第几次出现,不再一一具体指出)。
既然已知该区间深度最小值对应的编号,然后就可以通过之前深搜时写下的编号数组f找到对应的点,该点通过map已经存下了字符串和整型数对应的关系,进而也就找到了解。
数组f里面存的是第几次出现了哪个点。
这就是这段代码的核心思想,至于RMQ-ST算法的思想,我的另外一篇博客里面有,读者可以自己去找一下,也可以参观其它的博客。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<string>
#include <cmath>
using namespace std;
const int N = 200005;
struct edge
{
int to,nxt,d;
edge(int t = 0,int n = 0,int d = 0):to(t),nxt(n),d(d){}
}E[N*2];
int n;
int head[N*2],tot,deg[N];
int cnt,vis[N],f[N*2],rk[N*2],pos[N*2],dis[N],dp[N*2][35];
//f存储节点编号,rk存储节点深度,pos记录结点第一次出现的位置
map<string,int> mp;
string mm[N];
void init()
{
memset(head,-1,sizeof(head));
for(int i = 0;i < N;i++)
vis[i] = dis[i] = 0;
tot = cnt = 0;
mp.clear();
}
void add_edge(int s,int t,int d)
{
E[tot] = edge(t,head[s],d);
head[s] = tot++;
}
void dfs(int u,int depth)
{
vis[u] = 1;
f[++cnt] = u;
pos[u] = cnt;
rk[cnt] = depth;
for(int i = head[u];~i;i = E[i].nxt)//访问所有子结点
{
int v = E[i].to;
if(!vis[v])
{
//dis[v] = dis[u] + w;
dfs(v,depth+1);
f[++cnt] = u;
rk[cnt] = depth;
}
}
}
void RMQ(int n)
{
for(int i = 1;i <= n;i++)
dp[i][0] = i;//初始化dp数组,让数组里面区间长度为1的最小值设置为它本身
for(int j = 1;(1<<j) <= n;j++)
{
for(int i = 1;i+(1<<j)-1 <= n;i++)
{
int a = dp[i][j-1],b = dp[i + (1<<(j-1))][j-1];
dp[i][j] = rk[a] < rk[b] ? a : b;
}
}
}
int query(int l,int r)
{
int k = (int)(log(r - l + 1.0) / log(2.0));
int a = dp[l][k],b = dp[r-(1<<k)+1][k];
return rk[a] < rk[b] ? a : b;
}
int LCA(int u,int v)
{
int x = pos[u],y = pos[v];
if(x > y) swap(x,y);
int t = query(x,y);//找到深度最小的结点编号
return f[t];
}
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
int n,m;
init();
cin >> n;
string u,v;
int num = 0;
for(int i = 1;i <= n;i++)
{
cin >> u >> v;
if(mp[u] == 0)
mp[u] = ++num,mm[num] = u;
if(mp[v] == 0)
mp[v] = ++num,mm[num] = v;
//cout << mp[u] << " " << mp[v] << "\n";
add_edge(mp[u],mp[v],0);
add_edge(mp[v],mp[u],0);
deg[mp[v]]++;
}
dfs(1,1);
RMQ(cnt);
cin >> m;
while(m--)
{
cin >> u >> v;
int lca = LCA(mp[u],mp[v]);
cout << mm[lca] << "\n";
}
getchar();
getchar();
return 0;
}
最近公共祖先-三(RMQ-ST)的更多相关文章
- hihoCoder week17 最近公共祖先·三 lca st表
记录dfs序列,dfn[tot] 记录第tot次访问的节点 然后查两点在dfs序中出现的第一次 id[u] id[v] 然后 找 dep[k] = min( dep[i] ) {i 属于 [id[u ...
- hihocoder1069 最近公共祖先·三(tarjin算法)(并查集)
#1069 : 最近公共祖先·三 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 上上回说到,小Hi和小Ho使用了Tarjan算法来优化了他们的“最近公共祖先”网站,但是 ...
- 求LCA最近公共祖先的在线ST算法_C++
ST算法是求最近公共祖先的一种 在线 算法,基于RMQ算法,本代码用双链树存树 预处理的时间复杂度是 O(nlog2n) 查询时间是 O(1) 的 另附上离线算法 Tarjan 的链接: http ...
- 【hihoCoder第十七周】最近公共祖先·三
之前就写的是离线算法.思路就是先序一遍树,记录层数,然后高效RMQ就好.ST和线段树都能过. 以后有时间将之前的在线算法补上. #include <bits/stdc++.h> using ...
- LCA(最近公共祖先)——dfs+ST 在线算法
一.前人种树 博客:浅谈LCA的在线算法ST表 二.沙场练兵 题目:POJ 1330 Nearest Common Ancestors 题解博客:http://www.cnblogs.com/Miss ...
- hihoCoder#1069 最近公共祖先·三
原题地址 根据提示用Spase Table做 将Tree先展成List,因为数组长度等于边数的2倍,树中边数等于节点数-1,所以List数组只要开2倍节点数大小即可 WA了几次,原来是查询的时候出现左 ...
- 最近公共祖先(LCA)的三种求解方法
转载来自:https://blog.andrewei.info/2015/10/08/e6-9c-80-e8-bf-91-e5-85-ac-e5-85-b1-e7-a5-96-e5-85-88lca- ...
- 『图论』LCA 最近公共祖先
概述篇 LCA (Least Common Ancestors) ,即最近公共祖先,是指这样的一个问题:在一棵有根树中,找出某两个节点 u 和 v 最近的公共祖先. LCA 可分为在线算法与离线算法 ...
- 二叉树系列 - 求两节点的最低公共祖先,例 剑指Offer 50
前言 本篇是对二叉树系列中求最低公共祖先类题目的讨论. 题目 对于给定二叉树,输入两个树节点,求它们的最低公共祖先. 思考:这其实并不单单是一道题目,解题的过程中,要先弄清楚这棵二叉树有没有一些特殊的 ...
随机推荐
- mfc基于对话框的应用程序,如何设置初始对话框大小,移动控件位置
void MmPLEntPropertyDlg::SetInitDialogSize() { CRect rectDlg; GetWindowRect(rectDlg);//x,y为对话框左上角的坐标 ...
- python property的2种使用方法
一.property类 class Person(): def __init__(self, name): self.set_name(name) def get_name(self): return ...
- loj #6302. 「CodePlus 2018 3 月赛」寻找车位【线段树+单调队列】
考虑静态怎么做:枚举右边界,然后枚举上边界,对应的下边界一定单调不降,单调栈维护每一列从当前枚举的右边界向左最长空位的长度,这样是O(nm)的 注意到n>=m,所以m<=2000,可以枚举 ...
- Educational Codeforces Round 46 (Rated for Div. 2) B. Light It Up
Bryce1010模板 http://codeforces.com/problemset/problem/1000/B 思路:先用两个数组sumon[]和sumoff[]将亮着的灯和灭的灯累计一下. ...
- vue项目node升级后,node-saas报错解决办法
ERROR in ./node_modules/_extract-text-webpack-plugin@3.0.2@extract-text-webpack-plugin/dist/loader.j ...
- 浅析cookie
基本概念:cookie是指web浏览器存储的少量数据,该数据会在每次请求一个相关的URL时自动传到服务器中. 以博客园为例,我们看看cookie有哪些属性: 1.Name:cookie的名称: 2. ...
- springboot之项目打包
通过win中的cmd或者idea中终端,打包并启动项目: 1.mvn package [打包,在target中生成jar] 2.java -jar xxxxx.jar [启动jar]
- Android利用Socket与硬件通信之智能家居APP
前几天做一个智能家居APP,硬件段使用的是ESP8266WIFI模块,其实不管是WIFI模块还是蓝牙,通信都是同样一个道理,获取IP和端口来进行通信. 我是通过XCOM v2.0 发送信息,移动端接收 ...
- Mac上安装Homebrew和wget
实际上是使用Homebrew来安装wget 安装Homebrew Homebrew一般称为brew,是Mac OSX上的软件包管理工具,能在Mac中方便的安装软件或者卸载软件, 只需要一个命令, 非常 ...
- Halcon学习笔记1
转:https://www.cnblogs.com/hanzhaoxin/archive/2013/02/15/2912879.html 机器视觉工程应用主要可划分为硬件和软件两大部分. 硬件:工程应 ...