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

#1069 : 最近公共祖先·三

时间限制:10000ms

单点时限:1000ms

内存限制:256MB

描述

上上回说到,小Hi和小Ho使用了Tarjan算法来优化了他们的“最近公共祖先”网站,但是很快这样一个离线算法就出现了问题:如果只有一个人提出了询问,那么小Hi和小Ho很难决定到底是针对这个询问就直接进行计算还是等待一定数量的询问一起计算。毕竟无论是一个询问还是很多个询问,使用离线算法都是只需要做一次深度优先搜索就可以了的。

那么问题就来了,如果每次计算都只针对一个询问进行的话,那么这样的算法事实上还不如使用最开始的朴素算法呢!但是如果每次要等上很多人一起的话,因为说不准什么时候才能够凑够人——所以事实上有可能要等上很久很久才能够进行一次计算,实际上也是很慢的!

“那到底要怎么办呢?在等到10分钟,或者凑够一定数量的人两个条件满足一个时就进行运算?”小Ho想出了一个折衷的办法。

“哪有这么麻烦!别忘了和离线算法相对应的可是有一个叫做在线算法的东西呢!”小Hi笑道。

小Ho面临的问题还是和之前一样:假设现在小Ho现在知道了N对父子关系——父亲和儿子的名字,并且这N对父子关系中涉及的所有人都拥有一个共同的祖先(这个祖先出现在这N对父子关系中),他需要对于小Hi的若干次提问——每次提问为两个人的名字(这两个人的名字在之前的父子关系中出现过),告诉小Hi这两个人的所有共同祖先中辈分最低的一个是谁?

提示:最近公共祖先无非就是两点连通路径上高度最小的点嘛!

输入

每个测试点(输入文件)有且仅有一组测试数据。

每组测试数据的第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的询问,按照在输入中出现的顺序,各输出一行,表示查询的结果:他们的所有共同祖先中辈分最低的一个人的名字。

样例输入

4
Adam Sam
Sam Joey
Sam Micheal
Adam Kevin
3
Sam Sam
Adam Sam
Micheal Kevin

样例输出

Sam
Adam
Adam

分析:LCA的在线算法,RMQ-ST算法。留存作为模板。

从树的根节点开始进行深度优先搜索,每次经过某一个点——无论是从它的父亲节点进入这个点,还是从它的儿子节点返回这个点,都按顺序记录下来。这样,就把一棵树转换成了一个数组。而找到树上两个节点的最近公共祖先,无非就是找到这两个节点第一次出现在数组中的位置所囊括的一段区间中深度最小的那个点。所以,方法也就出来了。

步骤:

1)dfs计算出每个节点的深度depth[],每个节点第一次出现的位置first[]。id[]数组保存当前节点。

2)RMQ-ST算法进行预处理,minq[i][j]表示的是从i开始,长度为2^j区间内的深度最小值的下标。

3)直接输出结果。

RMQ-ST算法解释:http://blog.csdn.net/jhgkjhg_ugtdk77/article/details/47298969

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

代码清单:

#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<stack>
#include<ctime>
#include<cctype>
#include<string>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;

const int maxi = 20 + 5 ;
const int maxn = 100000 + 5;
const int maxv = 100000 + 5;

struct G{ int v,next; }graph[2*maxn];

int N,M;
string name1,name2;
string rstr[maxn];
map<string,int>idx;
int iid,idx1,idx2,num,idd;
int head[2*maxn];
int minq[2*maxn][maxi]; //这个数组记得开到2*N,因为遍历后序列长度为2*n-1
int first[2*maxn]; //每个节点在遍历序列中第一次出现的位置
int id[2*maxn];    //保存遍历的节点序列,长度为2n-1,从下标1开始保存
int depth[2*maxn]; //和遍历序列对应的节点深度数组,长度为2n-1,从下标1开始保存
bool vis[maxn];

void init(){
    idx.clear(); iid=idd=num=0;
    memset(head,-1,sizeof(head));
    memset(minq,0x7f,sizeof(minq));
    memset(first,0,sizeof(first));
    memset(id,0,sizeof(id));
    memset(depth,0,sizeof(depth));
    memset(vis,false,sizeof(vis));
}

int get_idx(string name){
    if(idx.count(name)) return idx[name];
    idx[name]=++iid; rstr[iid]=name; return iid;
}

void addg(int u,int v){
    graph[num].v=v;
    graph[num].next=head[u];
    head[u]=num++;
}

void input(){
    scanf("%d",&N);
    for(int i=1;i<=N;i++){
        cin>>name1>>name2;
        idx1=get_idx(name1);
        idx2=get_idx(name2);
        addg(idx1,idx2);
        addg(idx2,idx1);
    }
}

void dfs(int u,int dep){
    vis[u]=true; id[++idd]=u;
    depth[idd]=dep; first[u]=idd;
    for(int i=head[u];i!=-1;i=graph[i].next){
        int v=graph[i].v;
        if(vis[v]) continue;
        dfs(v,dep+1);
        id[++idd]=u; depth[idd]=dep;
    }
}

int min(int a,int b){ return depth[a]<depth[b] ? a : b ; }

void RMQ_ST(){
    int L=(int)((log(idd))/(log(2.0)));
    for(int i=1;i<=idd;i++) minq[i][0]=i;
    for(int j=1;j<=L;j++){
        for(int i=1;i+(1<<j)-1<=idd;i++){
            minq[i][j]=min(minq[i][j-1],minq[i+(1<<(j-1))][j-1]);
        }
    }
}

string RMQ(int l,int r){
    if(l>r) swap(l,r);
    int mi=(int)((log(r-l+1))/(log(2.0)));
    int pose=min(minq[l][mi],minq[r-(1<<mi)+1][mi]);
    return rstr[id[pose]];
}

void solve(){
    dfs(1,0);
    RMQ_ST();
    scanf("%d",&M);
    for(int i=1;i<=M;i++){
        cin>>name1>>name2;
        idx1=first[get_idx(name1)];
        idx2=first[get_idx(name2)];
        cout<<RMQ(idx1,idx2)<<endl;
    }
}

int main(){
    init();
    input();
    solve();
    return 0;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-06 05:34:02

hihoCoder_#1069_最近公共祖先·三(RMQ-ST模板)的相关文章

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,

[hiho 17]最近公共祖先 三

题目描述 这次是使用在线算法解决这个问题. 两个节点的最近公共祖先就是这两个节点的通路上深度最浅的那个节点. 可以通过一遍深搜把树转成数组:每次经过一个节点(无论是从父节点进入还是从子节点返回)时,把它放入数组.同时要记录每个节点在数组中最后一次出现的位置. 使用RMQ-ST算法预先计算2^k长度区间内深度最浅的节点编号. 对于每次询问,将其转换为两个区间段求解. #include <iostream> #include <algorithm> #include <cstri

hihoCoder_#1067_最近公共祖先&#183;二(LCA模板)

#1067 : 最近公共祖先·二 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 上上回说到,小Hi和小Ho用非常拙劣--或者说粗糙的手段山寨出了一个神奇的网站,这个网站可以计算出某两个人的所有共同祖先中辈分最低的一个是谁.远在美国的他们利用了一些奇妙的技术获得了国内许多人的相关信息,并且搭建了一个小小的网站来应付来自四面八方的请求. 但正如我们所能想象到的--这样一个简单的算法并不能支撑住非常大的访问量,所以摆在小Hi和小Ho面前的无非两种选择: 其一是购买更为昂

hihoCoder_#1062_最近公共祖先&#183;一

#1062 : 最近公共祖先·一 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Ho最近发现了一个神奇的网站!虽然还不够像58同城那样神奇,但这个网站仍然让小Ho乐在其中,但这是为什么呢? "为什么呢?"小Hi如是问道,在他的观察中小Ho已经沉迷这个网站一周之久了,甚至连他心爱的树玩具都弃置一边. "嘿嘿,小Hi,你快过来看!"小Ho招呼道. "你看,在这个对话框里输入我的名字,在另一个对话框里,输入你的名字,再点这个查询

最近公共祖先(LCA)模板

第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每行包含两个正整数x.y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树). 接下来M行每行包含两个正整数a.b,表示询问a结点和b结点的最近公共祖先. 输出格式: 输出包含M行,每行包含一个正整数,依次为每一个询问的结果. 输入样例#1: 5 5 4 3 1 2 4 5 1 1 4 2 4 3 2 3 5 1 2 4 5 输出样例#1: 4 4 1 4 4 模板:时间复杂度nlogn

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

树上任意两点的最近祖先,必定就是这两个节点的最短路径上深度最小的那个点. 例如:下图中,节点7和5,其最短路径为7--4--1--5, 这条路径上深度最小的点为节点1,其深度为1.节点1即为节点7和5的LCA. 因此,要找到任意两个节点的LCA,只需要先找到上述最短路径,再找到最短路径中深度最小的点.而这下面所述LCA在线算法所做的事. LCA在线算法描述(以上图为例): 1.获得“最短路径”(并不是真正的一条路径,包含其他节点,但不影响算法的正确性) 采用DFS遍历整棵树,得到以下数据: (1

【hihoCoder第十七周】最近公共祖先&#183;三

之前就写的是离线算法.思路就是先序一遍树,记录层数,然后高效RMQ就好.ST和线段树都能过. 以后有时间将之前的在线算法补上. #include <bits/stdc++.h> using namespace std; #define MAXN 100005 #define MAXM 105 #define inf 0x7ffffff int n; struct Edge { int v, next; } edge[MAXN]; int head[MAXN]; int e; void addE

hihoCoder#1069 最近公共祖先&#183;三

原题地址 根据提示用Spase Table做 将Tree先展成List,因为数组长度等于边数的2倍,树中边数等于节点数-1,所以List数组只要开2倍节点数大小即可 WA了几次,原来是查询的时候出现左边界大于右边界的情况,所以这种情况要颠倒一下 代码: 1 #include <iostream> 2 #include <vector> 3 #include <string> 4 #include <map> 5 6 using namespace std;

最近公共祖先(三种算法)

最近研究了一下最近公共祖先算法,根据效率和实现方式不同可以分为基本算法.在线算法和离线算法.下面将结合hihocoder上的题目分别讲解这三种算法. 1.基本算法 对于最近公共祖先问题,最容易想到的算法就是从根开始遍历到两个查询的节点,然后记录下这两条路径,两条路径中距离根节点最远的节点就是所要求的公共祖先. 题目参见 #1062 : 最近公共祖先·一 附上AC代码,由于记录的方式采取的是儿子对应父亲,所以实现的时候有点小技巧,就是对第一个节点的路径进行标记,查找第二个节点的路径时一旦发现访问到