hihoCoder_#1069 : 最近公共祖先 · 三(LCA 在线算法 -- DFS+RMQ-ST) 详解

原题链接

解题思路

1、建树

2、用 dfs 将树转换为一维数组

3、rmq(st算法) 预处理 查找

 

第1步 vector建树,这步不难理解,看代码可以看懂。 

 

第2步 dfs转换过程

记录规则就是假设在走路,不管走没走过的地方,只要经过就记录。

记录下 dfs 的序列,还有 dfs 过程中每一个点的深度(即辈分)。还需要记录一个在 dfs 中每一个节点首次出现的位置。

例1

不停地 走左下,回溯,走右下,则:

数组 f 表示经过的序号

与f序号对应的深度(辈分)数组命名为dep

则上图对应的:

f      1 2 3 4 3 5 3 2 6 2 1 7 1

dep 1 2 3 4 3 4 3 2 3 2 1 2 1

 

pos数组表示图中1-7每个点在f数组第一次出现的位置:1 2 3 4 6 9 12

查询两点间的最小公共祖先,即第3步的查找,就是在f数组中两个点第一次出现的位置(即pos)中间的点中找辈分最小的。

比如要找点4和点5的公共祖先,pos[4] = 4(第一次出现的位置为f数组的第四位),pos[5] = 6(同理)。那么在f数组的第四位和第六位中间只有一个3,所以3就是最小公共祖先,如果中间有很多,则需要找到辈分最小的(即dep最大的),就是最小公共祖先。

 

第3步 rmq(st算法) 预处理,查找

RMQ (Range Minimum/Maximum Query) 问题是指:对于长度为 n 的数列 A,回答若干询问 RMQ(A,i,j)(i,j<=n),返回数列 A 中下标在 i,j 里的最小 (大)值,也就是说,RMQ 问题是指求区间最值的问题。

这里我们查找的是i到j区间中深度dep的最大值(即辈分最小)

然后输出深度最大值(即辈分最小)对应的名字即可。

 

题解代码:

#include <iostream>
#include <cstdio>
#include <map>
#include <vector>
#include <cmath>

using namespace std;

const int maxn = 1e5+10;

int n,m; ///n:父子关系数据数量 m:询问数量

map<string, int>mp;
int tot;
string name[maxn];
vector<int>vec[maxn];
int root;

int f[maxn], dep[maxn], pos[maxn];
int dp[maxn][100];

void dfs(int idx, int depth) /// 预处理f,dep,pos数组
{
    f[++tot] = idx;
    dep[tot] = depth; ///标记深度
    pos[idx] = tot;   ///f,pos,dep数组作用看上面的例1。

    for(int i = 0; i < vec[idx].size(); ++i)
    {
        dfs(vec[idx][i], depth+1);
        f[++tot] = idx;
        dep[tot] = depth;
    }
}

void st()
{
    for(int i = 1; i <= tot; ++i)
        dp[i][0] = i;
    for(int j = 1; (1<<j) <= tot; ++j)
    {
        for(int i = 1; i+(1<<j)-1 <= tot; ++i)
        {
            int mid = i + (1 << (j-1)); ///将区间分成长度相同的两段,mid为右区间第一个
            if(dep[dp[i][j-1]] < dep[dp[mid][j-1]])
                dp[i][j] = dp[i][j-1];
            else
                dp[i][j] = dp[mid][j-1];
        }
    }
}

int rmq(int l, int r)
{
    l = pos[l];
    r = pos[r];
    if(l > r)
        swap(l,r);
    int len = r-l+1;
    int len2 = log2(len);
    if(dep[dp[l][len2]] < dep[dp[r-(1<<len2)+1][len2]])
        return dp[l][len2];
    return dp[r-(1<<len2)+1][len2];
}

int main()
{
    cin >> n;
    string a, b; /// a父 b子
    
    ///建树
    for(int i = 0; i < n; ++i)
    {
        cin >> a >> b;
        /// map容器mp 和 name数组 就是存一下名字字符串及序号,不必在乎顺序
        if(!mp[a]) /// !mp[a] 即 mp[a]为空,下面同理
        {
            mp[a] = ++tot;
            name[mp[a]] = a;
        }
        if(!mp[b])
        {
            mp[b] = ++tot;
            name[mp[b]] = b;
        }
        vec[mp[a]].push_back(mp[b]); /// 向父节点后添加子节点
        if(!i)
            root = mp[a]; /// 题中说了:每个输入文件中第一个出现的名字所确定的人是其他所有人的公共祖先。
    }

    ///dfs转换一维数组
    tot = 0;
    dfs(root, 0);
    
    st(); ///rmq预处理
    
    ///rmq查找
    cin >> m;
    while(m--)
    {
        cin >> a >> b;
        int aa = mp[a], bb = mp[b];
        int ans = rmq(aa,bb); /// ans 为 aa 到 bb 的辈分最小共同祖先的序号
        cout << name[f[ans]] << endl;
    }
    return 0;
}

 

posted @ 2018-08-09 01:19  shiyanini  阅读(293)  评论(0编辑  收藏  举报