AcWing 1644. 二叉树中的最低公共祖先 题解 线性dp 倍增算法 前序中序构造二叉树

本文主要是介绍AcWing 1644. 二叉树中的最低公共祖先 题解 线性dp 倍增算法 前序中序构造二叉树,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

二叉树中的最低公共祖先

题目描述

树中两个结点 U 和 V 的最低公共祖先(LCA)是指同时具有 U 和 V 作为后代的最深结点。给定二叉树中的任何两个结点,请你找到它们的 LCA。

输入描述

第一行包含两个整数 M 和 N ,分别表示询问结点对数以及二叉树中的结点数量。
接下来两行,每行包含 N 个不同的整数,分别表示二叉树的中序和前序遍历。保证二叉树可由给定遍历序列唯一确定。
接下来 M 行,每行包含两个整数 U 和 V ,表示一组询问。

输出描述

对于每对给定的 U 和 V ,输出一行结果:
如果 U 和 V 的 LCA 是 A ,且 A 不是 U 或 V ,则输出 “LCA of U and V is A.”
如果 U 和 V 的 LCA 是 A ,且 A 是 U 或 V 中的一个,则输出 “X is an ancestor of Y.” ,其中 X 表示 A , Y 表示另一个结点。
如果 U 或 V 没有在二叉树中找到,则输出 “ERROR: U is not found.” 或 “ERROR: V is not found.” 或 “ERROR: U and V are not found.”。

用例输入

6 8
7 2 3 4 6 5 1 8
5 3 7 2 6 4 8 1
2 6
8 1
7 9
12 -3
0 8
99 99

用例输出

LCA of 2 and 6 is 3.
8 is an ancestor of 1.
ERROR: 9 is not found.
ERROR: 12 and -3 are not found.
ERROR: 0 is not found.
ERROR: 99 and 99 are not found.

数据规模与约定

所有结点权值均在 int 范围内。
1 ≤ M ≤ 1000,1 ≤ N ≤ 10000。

题目链接

AcWing1644——传送门

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;const int maxn = 1e4 + 6;vector<int> e[maxn];    // 以第i个节点为起点的有向边
int depth[maxn];        // 第i个节点的深度
int ancestor[maxn][23]; // ancestor[i][j]表示节点i的2^j级祖先
int lg[maxn];           // 预处理第i个节点的lg值void add(int x, int y) // 加有向边函数
{e[x].push_back(y);
}void dfs(int u, int fath) // dfs计算深度和祖先
{ancestor[u][0] = fath;                                    // 第1级祖先即为父亲depth[u] = depth[fath] + 1;                               // 深度为父亲深度+1for (int j = 1; j <= lg[depth[u]]; j++)                   // 计算该节点的2^j级祖先ancestor[u][j] = ancestor[ancestor[u][j - 1]][j - 1]; // 祖先的转移方程,u的2^j祖先等于u的2^(j-1)祖先的2^(j-1)祖先for (int i = 0; i < e[u].size(); i++)                     // 递归子节点{int v = e[u][i];if (fath != v)dfs(v, u);}
}map<int, int> idx; // 离散化后的下标
map<int, int> num; // 下标所对应的原来的值void LCA(int x, int y) // 计算x和y的LCA
{// 将x和y先记录下来,便于输出答案int u = x;int v = y;// 将x和y转化为离散后的下标x = idx[x];y = idx[y];bool tag = 0;            // 记录是否发生了交换if (depth[x] < depth[y]) // 让x为深度更大(或相等)的那一个{swap(x, y);tag = 1;}while (depth[x] > depth[y]) // 将x提到与y相同深度x = ancestor[x][lg[depth[x] - depth[y]] - 1];if (x == y){if (tag == 0)cout << v << " is an ancestor of " << u << "." << '\n';elsecout << u << " is an ancestor of " << v << "." << '\n';return;}for (int k = lg[depth[x]] - 1; k >= 0; k--) // 倍增跳跃if (ancestor[x][k] != ancestor[y][k])x = ancestor[x][k], y = ancestor[y][k];cout << "LCA of " << u << " and " << v << " is " << num[ancestor[x][0]] << "." << '\n';return;
}map<int, int> mp;
int lc[maxn];
int rc[maxn];
int traversal(int pre_st, int pre_end, int in_st, int in_end, vector<int> &in, vector<int> &pre)
{int cur_len = pre_end - pre_st; // 当前区间长度if (cur_len == 0)return 0;int root = pre[pre_st]; // 找到根节点if (cur_len == 1)return root;int pos = mp[root];    // 根位置int len = pos - in_st; // 左子树长度// 递归构造左子树和右子树lc[root] = traversal(pre_st + 1, pre_st + len + 1, in_st, pos, in, pre);rc[root] = traversal(pre_st + len + 1, pre_end, pos + 1, in_end, in, pre);return root;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);int n, m, root; // 节点个数,询问个数,根节点cin >> m >> n;vector<int> in(n);vector<int> pre(n);map<int, bool> exist; // 记录某点是否存在// 中序序列for (int i = 0; i < n; i++){cin >> in[i];exist[in[i]] = 1;}// 离散化各个点为1~nint i = 1;for (auto it = exist.begin(); it != exist.end(); it++, i++){idx[it->first] = i;num[i] = it->first;}// 将中序序列改为离散后的下标for (int i = 0; i < n; i++){in[i] = idx[in[i]];}// 记录在中序序列中的位置for (int i = 0; i < n; i++){mp[in[i]] = i;}// 前序序列for (int i = 0; i < n; i++){cin >> pre[i];}// 将前序序列改为离散后的下标for (int i = 0; i < n; i++){pre[i] = idx[pre[i]];}// 构造二叉树root = traversal(0, n, 0, n, in, pre);// 记录树中的边for (int i = 1; i <= n; i++){if (lc[i] != 0){add(i, lc[i]);add(lc[i], i);}if (rc[i] != 0){add(i, rc[i]);add(rc[i], i);}}// 预处理每个深度值的lg值for (int i = 1; i <= n; ++i)lg[i] = lg[i - 1] + (1 << lg[i - 1] == i); // 非常厉害的转移方程// 预处理节点i的2^j级祖先dfs(root, 0);// 处理询问int x, y;for (int i = 1; i <= m; ++i){cin >> x >> y;if (exist[x] == 0 && exist[y] == 0){cout << "ERROR: " << x << " and " << y << " are not found." << '\n';}else if (exist[x] == 0){cout << "ERROR: " << x << " is not found." << '\n';}else if (exist[y] == 0){cout << "ERROR: " << y << " is not found." << '\n';}else{LCA(x, y);}}return 0;
}

这篇关于AcWing 1644. 二叉树中的最低公共祖先 题解 线性dp 倍增算法 前序中序构造二叉树的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/975161

相关文章

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

康拓展开(hash算法中会用到)

康拓展开是一个全排列到一个自然数的双射(也就是某个全排列与某个自然数一一对应) 公式: X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0! 其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。(a[i]在不同应用中的含义不同); 典型应用: 计算当前排列在所有由小到大全排列中的顺序,也就是说求当前排列是第

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu4826(三维DP)

这是一个百度之星的资格赛第四题 题目链接:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1004&cid=500 题意:从左上角的点到右上角的点,每个点只能走一遍,走的方向有三个:向上,向下,向右,求最大值。 咋一看像搜索题,先暴搜,TLE,然后剪枝,还是TLE.然后我就改方法,用DP来做,这题和普通dp相比,多个个向上

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

hdu1011(背包树形DP)

没有完全理解这题, m个人,攻打一个map,map的入口是1,在攻打某个结点之前要先攻打其他一个结点 dp[i][j]表示m个人攻打以第i个结点为根节点的子树得到的最优解 状态转移dp[i][ j ] = max(dp[i][j], dp[i][k]+dp[t][j-k]),其中t是i结点的子节点 代码如下: #include<iostream>#include<algorithm

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

hdu4865(概率DP)

题意:已知前一天和今天的天气概率,某天的天气概率和叶子的潮湿程度的概率,n天叶子的湿度,求n天最有可能的天气情况。 思路:概率DP,dp[i][j]表示第i天天气为j的概率,状态转移如下:dp[i][j] = max(dp[i][j, dp[i-1][k]*table2[k][j]*table1[j][col] )  代码如下: #include <stdio.h>#include