最近公共祖先-三(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
前言 本篇是对二叉树系列中求最低公共祖先类题目的讨论. 题目 对于给定二叉树,输入两个树节点,求它们的最低公共祖先. 思考:这其实并不单单是一道题目,解题的过程中,要先弄清楚这棵二叉树有没有一些特殊的 ...
随机推荐
- (水题)洛谷 - P1598 - 垂直柱状图
https://www.luogu.org/problemnew/show/P1598 忘记读取后清空数组,也不知道准确的长度. #include<bits/stdc++.h> using ...
- python 模块和包的使用方法
一.模块 1.import导入模块 import module1,mudule2... 2.from...import...导入模块 导入指定内容 from modname import name1[ ...
- P1648 看守
传送门 以二维的两个点\((x1,y1),(x2,y2)\)为例,那么他们之间的曼哈顿距离肯定为一下四个之一\((x1-x2)+(y1-y2)\),\((x2-x1)+(y1-y2)\),\((x1- ...
- C++结构体的应用_YCOJ
结构体是一种自定义的东西,用struct来定义.在他里面, 可以装许多东西,比如int,string,char,bool等等等等. 如: struct a{ string name; int a; i ...
- hdu 2189 悼念512汶川大地震遇难同胞——来生一起走 基础母函数
#include <iostream> #include <algorithm> #include <cstring> using namespace std; ] ...
- hdu3038 How Many Answers Are Wrong 种类并查集
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; int ...
- 洛谷p2922[USACO08DEC]秘密消息Secret Message
题目: 题目链接:[USACO08DEC]秘密消息Secret Message 题意: 给定n条01信息和m条01密码,对于每一条密码A,求所有信息中包含它的信息条数和被它包含的信息条数的和. 分析: ...
- bzoj2154||洛谷P1829 Crash的数字表格&&JZPTAB && bzoj3309 DZY Loves Math
bzoj2154||洛谷P1829 https://www.lydsy.com/JudgeOnline/problem.php?id=2154 https://www.luogu.org/proble ...
- java基本数据类型在栈中怎么存放的?
参考地址:https://www.zhihu.com/question/24747160 问:int a = 3; 首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟 ...
- Linux下mysql新建账号及权限设置各种方式总结
来自:http://justcoding.iteye.com/blog/1941116 1.权限赋予 说明:mysql部署在服务器A上,内网上主机B通过客户端工具连接服务器A以进行数据库操作,需要服务 ...