题目

{% fold 点击显/隐题目 %}

上上回说到,小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
{% endfold %}

题解

维护下每个人的父亲
询问时,从第一个询问的人开始向上标记
第二个人向上查询,第一个访问到的带标记的就是公共祖先

时间复杂度 O(log n)

代码

{% fold 点击显/隐代码 %}```cpp 最近公共祖先·三 https://github.com/OhYee/sourcecode/tree/master/ACM 代码备份
#include
#include
#include
#include
#include
using namespace std;

const int maxn = 1e5+5;

map<string, int> Hash;
vector HashList;
int parent[maxn];
bool vis[maxn];

void init() {
Hash.clear();
HashList.clear();
memset(parent, -1, sizeof(parent));
}

int getHash(string s) {
if (!Hash.count(s)) {
Hash.insert(make_pair(s, HashList.size()));
HashList.push_back(s);
}
return Hash[s];
}

void addEdge(int u, int v) { parent[v] = u; }

int find_parent(int n) {
if (n == -1)
return -1;
if (vis[n])
return n;
vis[n] = true;
return find_parent(parent[n]);
}

int main() {
int n;
while (cin >> n) {
init();
for (int i = 0; i < n; i++) {
string s1, s2;
cin >> s1 >> s2;
int u = getHash(s1);
int v = getHash(s2);
addEdge(u, v);
}

    int k;
    scanf("%d", &k);
    for (int i = 0; i < k; i++) {
        string s1, s2;
        cin >> s1 >> s2;
        int u = getHash(s1);
        int v = getHash(s2);
        memset(vis, false, sizeof(vis));
        find_parent(u);
        cout << HashList[find_parent(v)] << endl;
    }
}

}

{% endfold %}