hiho 17 最近公共祖先(三) 转换为区间最值查找 ST

问题描述

原题链接

题目分析

之前的对这个问题有两种解法,解法一是对每次查询,都从根节点向下查询,复杂度为O(N*M), 第二种解法是先收集足够多的查询,然后深度搜索+并查集的方法找到每个结果,但是这种方法不能实时查询。下面我们将问题转换为区间查找问题,查询复杂度为O(1),预处理复杂度为O(N*logN)。

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

#include <bits/stdc++.h>
using namespace std;
enum {maxn = 100000+5, maxnLog2= 17};
vector<int> tree[maxn];
int lastPos[maxn];
int depth[maxn];
int pre_cal[3*maxn][maxnLog2+2];
int nodeNum;
vector<string> names;
unordered_map<string, int> nameToid;
void dfs(int rt, int d)
{
    depth[rt] = d;
    pre_cal[nodeNum++][0] = rt;// 进入该节点
    for(int i=0; i< tree[rt].size(); i++)
    {
        dfs(tree[rt][i], d+1);
        pre_cal[nodeNum++][0] = rt; // 从子节点返回
    }
    lastPos[rt] = nodeNum;
    pre_cal[nodeNum++][0] = rt;
}
int main()
{
    int n;
    scanf("%d", &n);
    string a, b;
    int A, B;
    for (int i=0; i< n; i++)
    {
        cin>>a>>b;
        if (!nameToid.count(a)) A = nameToid[a] = names.size(), names.push_back(a);
        else A = nameToid[a];
        if (!nameToid.count(b)) B = nameToid[b] = names.size(), names.push_back(b);
        else B = nameToid[b];
        tree[A].push_back(B);
    }
    nodeNum = 0;
    dfs(0, 0);
    for(int i=1, l=2; l+0<= nodeNum; l*=2, i++)
    {
        for (int j=0; j+l<= nodeNum; j++)
        {
            if (depth[pre_cal[j][i-1]] < depth[pre_cal[j+l/2][i-1]])
                pre_cal[j][i] = pre_cal[j][i-1];
            else
                pre_cal[j][i] = pre_cal[j+l/2][i-1];
        }
    }
    int m;
    for(cin>>m; m; m--)
    {
        cin>>a>>b;
        A = lastPos[nameToid[a]];
        B = lastPos[nameToid[b]];
        if (A > B) swap(A, B);// 注意!。
        int t = maxnLog2+2;
        int len = B+1-A;
        while((1<<t) > len) t--;
        int res;
        if (depth[pre_cal[A][t]] < depth[pre_cal[B+1 - (1<<t)][t]])
            res = pre_cal[A][t];
        else
            res = pre_cal[B+1-(1<<t)][t];
        cout<<names[res]<<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值