hihocoder1067[最近公共祖先·二] DFS序+ST表

本文介绍了一种使用ST表求解最近公共祖先(LCA)问题的方法。该方法通过预处理父子关系构建并查集,实现快速查找任意两人最近公共祖先的功能。适用于大量查询场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

描述

上上回说到,小Hi和小Ho用非常拙劣——或者说粗糙的手段山寨出了一个神奇的网站,这个网站可以计算出某两个人的所有共同祖先中辈分最低的一个是谁。远在美国的他们利用了一些奇妙的技术获得了国内许多人的相关信息,并且搭建了一个小小的网站来应付来自四面八方的请求。

但正如我们所能想象到的……这样一个简单的算法并不能支撑住非常大的访问量,所以摆在小Hi和小Ho面前的无非两种选择:

其一是购买更为昂贵的服务器,通过提高计算机性能的方式来满足需求——但小Hi和小Ho并没有那么多的钱;其二则是改进他们的算法,通过提高计算机性能的利用率来满足需求——这个主意似乎听起来更加靠谱。

于是为了他们第一个在线产品的顺利运作,小Hi决定对小Ho进行紧急训练——好好的修改一番他们的算法。

而为了更好的向小Ho讲述这个问题,小Hi将这个问题抽象成了这个样子:假设现小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


solution:ST表求LCA

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
const int N = 1e5 + 7;
struct Edge{
    int nxt, to;
}e[N<<1];
int head[N], tot=0;
void addeage(int u, int v){
    e[++tot].nxt=head[u]; e[tot].to=v;
    head[u]=tot;
}
int pos[N], cnt;
struct T{
    int dep, v;
}dfn[N<<1];
void dfs(int u, int fa, int dep){
    dfn[++cnt].v=u, dfn[cnt].dep=dep;
    if( !pos[u] ) pos[u]=cnt;
    for ( int i=head[u]; i; i=e[i].nxt ){
        int v=e[i].to;
        if( v==fa ) continue;
        dfs(v, u, dep+1); 
        dfn[++cnt].v=u, dfn[cnt].dep=dep;
    }
}
T st[N<<1][25];
T minv(T a, T b){
    if( a.dep<b.dep ) return a;
    else return b;  
}
void init_st(){
    for ( int i=1; i<=cnt; i++ ) st[i][0]=dfn[i]; 
    for ( int j=1; (1<<j)<=cnt; j++ )
        for ( int i=1; i+(1<<j)-1<=cnt; i++ )
            st[i][j]=minv(st[i][j-1],st[i+(1<<(j-1))][j-1]); 
} 
T RMQ(int l, int r){
    int len=r-l+1;
    int j=0;
    while( 1<<(j+1)<=len ) j++;
    return minv(st[l][j], st[r-(1<<j)+1][j]);
}
map<string,int> mp1;
map<int,string> mp2;
int mpt=0;
int HASH(string str){
    if( mp1.count(str) ) return mp1[str];
    mp1[str]=++mpt;
    mp2[mpt]=str;
    return mpt; 
}
void swap(int &a, int &b){
    a=a^b;
    b=a^b;
    a=a^b;
} 
int main(){
    int n, root=0;
    scanf("%d", &n );
    for ( int i=1; i<=n; i++ ){
        string father, son;
        cin>>father>>son;
        int l=HASH(father), r=HASH(son);
        if(!root) root=l;
        addeage(l, r), addeage(r, l);
    } 
    dfs(root,root,1);
    init_st();
    int m;
    scanf("%d", &m );
    while( m-- ){
        string father, son;
        cin>>father>>son;
        int l=HASH(father), r=HASH(son);
        if( pos[l]>pos[r] ) swap(l,r);
        T p=RMQ(pos[l],pos[r]);
        cout<<mp2[p.v]<<endl;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值