思路:
题目的主要信息:
- 有两个结点数都为n,根都是1的树,设根的深度为0
- 定义点对(x,y)的价值为树1中x和y的最近公共祖先的深度+树2中a[x]和a[y]的最近公共祖先的深度
- 输出所有点对的最大值价值数组b与数组c分别记录树1与树2的各个节点的父节点
方法一:暴力解法(超时)
具体做法:
我们可以遍历树1的每一个点对,求得其在树1中得最近公共祖先深度,然后再用数组a转换后去求这两个点在树2中的公共祖先深度,二者相加,维护最大值。求LCA的时候,我们也是从这个节点依次往上遍历直到根,我们记录这其中的路径,然后从上往下最后一个相同的节点就是最近公共节点,记录其深度即可。
class Solution {
public:
int LCA(int x, int y, vector<int>& z){ //暴力LCA,记录祖先路径再从上往下找最近的公共祖先
if(x == 0)
return 0;
vector<int> path1; //记录节点x的路径
vector<int> path2; //记录节点y的路径
int temp = x;
while(x != 0){ //x遍历祖先直到0
if(z[x] != 0 && z[x] != y){
x = z[x];
path1.push_back(x); //记录往上的路径
}else if(z[x] == y)
return 0;
else if(z[x] == 0)
break;
}
path1.push_back(0);
while(y != 0){ //y遍历祖先直到0
if(z[y] != 0 && z[y] != temp){
y = z[y];
path2.push_back(y); //记录往上的路径
}else if(z[y] == temp)
return 0;
else if(z[y] == 0)
break;
}
path2.push_back(0);
int res = -1;
int n = path1.size() - 1;
int m = path2.size() - 1;
while(n >= 0 && m >= 0 && path1[n] == path2[m]){ //从上往下找到第一个不同的祖先
n--;
m--;
res++;
}
return res;
}
int wwork(int n, vector<int>& a, vector<int>& b, vector<int>& c) {
int res = 0;
for(int i = 1; i < n; i++){
for(int j = i + 1; j <= n; j++){ //遍历每一个点对
int temp = (LCA(i, j, b) + LCA(a[i], a[j], c)); //计算价值
res = temp > res ? temp : res; //维护最大值
}
}
return res;
}
};复杂度分析:
- 时间复杂度:
,遍历所有点对,每个点对暴力求解LCA平均为
,但是最坏情况可以到
- 空间复杂度:
,记录路径的辅助数组长度最长为
(退化为链表)
方法二:DFS + LCA
具体做法:
我们首先利用dfs遍历树2,找到节点的遍历次序,及从次序到到节点的关系,再记录树2每个节点的深度,然后记录树2祖先的祖先,为LCA倍增算法做预处理。用set维护树2的dfs次序建立虚树,利用dfs遍历树1枚举其每一个LCA,不断合并树2虚树的集合,其中set的lower_bound对应LCA最大的点,既可以找到我们所求的最大价值。
对于上述方法一求LCA的暴力算法,我们每次条一步,直到到根节点,但是对于倍增算法,我们对应的则是走1步、2步、4步……以此来倍增,通过dfs遍历,记录每个节点的次序及深度,并初始化求出树上每个节点u的祖先father[u][i]。求最近公共祖先时,根据两个节点的的深度,如不同,向上调整深度大的节点,使得两个节点在同一层上,如果正好是祖先结束,否则,将两个节点同时上移,查询最近公共祖先。(具体可以查看注释,注释很详细)
class Solution {
public:
vector<vector<int> > edge; //保存边
vector<set<int>> Q;
vector<vector<int>> father;
vector<int> dfn, redfn, depth;
int res = 0;
int count = 0;
void dfs1(int v, int deep){
count++;
dfn[v] = count; //dfs遍历次序
redfn[count] = v; //由次序到节点
depth[v] = deep; //记录树2某个节点的深度
for(int i = 0; i < edge[v].size(); i++){
dfs1(edge[v][i], deep + 1);
father[edge[v][i]][0] = v; //记录父节点
}
}
int LCA(int a, int b){ //倍增寻找最近公共祖先
if(depth[a] < depth[b]) //a一定在下面
swap(a, b);
for(int i = 17; i >= 0; i--)
if(depth[a] - depth[b] >= 1 << i) //找父亲节点
a = father[a][i];
if(a == b)
return a;
for(int i = 17; i >= 0; i--){
if(father[a][i] != father[b][i]){ //从最大祖先开始,判断a,b祖先,是否相同
a = father[a][i]; //如不相同,a b同时向上移动2^i
b = father[b][i];
}
}
return father[a][0];
}
int merge(set<int>& A, set<int>& B){ //合并AB集合
if(A.size() < B.size())
A.swap(B); //A为大集合,B为小集合
int best = 0;
for(int i: B){
auto iter = A.lower_bound(i); //二分查找,找到A中第一个大于i的
if(iter != A.end()) //找到
best = max(best, depth[LCA(redfn[i], redfn[*iter])]);
if(iter != A.begin()){ //不是第一个,向前缩
iter--;
best = max(best, depth[LCA(redfn[i], redfn[*iter])]);
}
A.insert(i);
}
B.clear();
return best;
}
void dfs2(int v, int deep){ //遍历树1的节点
int best = 0;
for(int i = 0; i < edge[v].size(); i++){
dfs2(edge[v][i], deep + 1);
best = max(best, merge(Q[v], Q[edge[v][i]]));
}
res = max(res, best + deep); //最近的公共祖先加上本来的层
}
int wwork(int n, vector<int>& a, vector<int>& b, vector<int>& c) {
edge.resize(n + 1);
dfn.resize(n + 1, 0);
redfn.resize(n + 1, 0);
depth.resize(n + 1, 0);
Q.resize(n + 1);
father.resize(n + 1, vector<int>(18, 0)); //测试数据树最大深度不超过18
for(int i = 2; i <= n; i++)
edge[c[i]].push_back(i); //树2的父节点连接子节点的边
dfs1(1, 0); // 对树2深度优先搜索记录其父节点
for(int i = 1; i <= 17; i++) //初始化LCA
for(int j = 1; j <= n; j++)
father[j][i] = father[father[j][i - 1]][i - 1]; //往后依次是往上的祖先节点
for(int i = 0; i <= n; i++) //清空树2的边
edge[i].clear();
for(int i = 2; i <= n; i++)
edge[b[i]].push_back(i); //树1的父节点连接子节点的边
for(int i = 1; i <= n; i++)
Q[i].insert(dfn[a[i]]); //树2对应节点进入集合
dfs2(1, 0);
return res;
}
};复杂度分析:
- 时间复杂度:
,主体函数循环都是
,两次dfs也都是
,合并函数为
,LCA函数为
- 空间复杂度:
,各个一维辅助数组的长度都为
,二维的辅助数组第二维都是常数,故看成一维

京公网安备 11010502036488号