问题描述
题目分析
之前的对这个问题有两种解法,解法一是对每次查询,都从根节点向下查询,复杂度为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;
}