当前位置: 首页 > news >正文

题解:AcWing 1172 祖孙询问

【题目来源】

AcWing:1172 祖孙询问 - AcWing题库

【题目描述】

给定一棵包含 \(n\) 个节点的有根无向树,节点编号互不相同,但不一定是 \(1\sim n\)

\(m\) 个询问,每个询问给出了一对节点的编号 \(x\)\(y\),询问 \(x\)\(y\) 的祖孙关系。

【输入】

输入第一行包括一个整数 \(n\) 表示节点个数;

接下来 \(n\) 行每行一对整数 \(a\)\(b\),表示 \(a\)\(b\) 之间有一条无向边。如果 \(b\)\(-1\),那么 \(a\) 就是树的根;

\(n+2\) 行是一个整数 \(m\) 表示询问个数;

接下来 \(m\) 行,每行两个不同的正整数 \(x\)\(y\),表示一个询问。

【输出】

对于每一个询问,若 \(x\)\(y\) 的祖先则输出 \(1\),若 \(y\)\(x\) 的祖先则输出 \(2\),否则输出 \(0\)

【输入样例】

10
234 -1
12 234
13 234
14 234
15 234
16 234
17 234
18 234
19 234
233 19
5
234 233
233 12
233 13
233 15
233 19

【输出样例】

1
0
0
0
2

【核心思想】

  1. 问题分析:给定一棵包含 \(n\) 个节点的有根无向树,节点编号互不相同,有 \(m\) 个询问,每个询问给出节点对 \((x, y)\),需要判断 \(x\)\(y\) 的祖孙关系。这是一个最近公共祖先(LCA)问题,通过判断 LCA 与询问节点的关系来确定祖孙关系。

  2. 算法选择

    • 倍增法求 LCA:预处理每个节点向上走 \(2^k\) 步到达的祖先节点,支持 \(O(\log n)\) 时间复杂度的 LCA 查询
    • BFS 预处理:从根节点出发进行 BFS,计算每个节点的深度 \(dep[]\) 和倍增祖先数组 \(fa[][]\)
    • 祖孙关系判定
      • \(LCA(x, y) = x\),则 \(x\)\(y\) 的祖先,输出 \(1\)
      • \(LCA(x, y) = y\),则 \(y\)\(x\) 的祖先,输出 \(2\)
      • 否则 \(x\)\(y\) 无祖孙关系,输出 \(0\)
  3. 关键步骤

    • 建树与找根
      • 读入 \(n\) 条边 \((a, b)\),使用邻接表存储树
      • \(b = -1\),则 \(a\) 为根节点
    • BFS 预处理(从根节点开始):
      • 初始化深度数组 \(dep[]\) 为无穷大,\(dep[root] = 1\)
      • 队列中加入根节点,进行 BFS 遍历
      • 对于每个节点 \(j\),记录 $fa[j][0] = $ 父节点(向上走 \(2^0 = 1\) 步)
      • 倍增预处理:对于 \(k\)\(1\)\(D\)\(D = 15\),因为 \(2^{15} > 40000\)):
        • \(fa[j][k] = fa[fa[j][k-1]][k-1]\)\(j\) 向上走 \(2^k\) 步等于先走 \(2^{k-1}\) 步再走 \(2^{k-1}\) 步)
    • LCA 查询(节点 \(x\)\(y\)):
      • 深度对齐:若 \(dep[x] < dep[y]\),交换 \(x\)\(y\),确保 \(x\) 深度较大
      • 提升 \(x\):从 \(k = D\)\(0\),若 \(dep[fa[x][k]] \geq dep[y]\),则 \(x = fa[x][k]\)(将 \(x\) 提升到与 \(y\) 同一深度)
      • 判断祖先:若 \(x = y\),返回 \(x\)\(y\)\(x\) 的祖先)
      • 同步提升:从 \(k = D\)\(0\),若 \(fa[x][k] \neq fa[y][k]\),则 \(x = fa[x][k]\)\(y = fa[y][k]\)(同时向上跳,直到 LCA 的下一层)
      • 返回 \(fa[x][0]\)(LCA)
    • 回答询问:对每个询问 \((a, b)\),计算 \(p = LCA(a, b)\),根据 \(p\)\(a, b\) 的关系输出 \(1, 2\)\(0\)
  4. 时间/空间复杂度

    • 预处理时间复杂度:\(O(n \log n)\),BFS \(O(n)\),倍增预处理每个节点 \(O(\log n)\)
    • 单次 LCA 查询时间复杂度:\(O(\log n)\),每次查询最多跳 \(\log n\)
    • 总时间复杂度:\(O(n \log n + m \log n)\)
    • 空间复杂度:\(O(n \log n)\)\(fa[][]\) 数组需要 \(n \times \log n\) 的空间
  5. 倍增法 LCA 的核心思想

    • 二进制拆分:将"向上走 \(k\) 步"拆分为二进制表示,如走 \(13\) 步 = 走 \(8 + 4 + 1\) 步,对应 \(2^3 + 2^2 + 2^0\)
    • 倍增预处理\(fa[x][k]\) 表示节点 \(x\) 向上走 \(2^k\) 步到达的祖先,通过递推 \(fa[x][k] = fa[fa[x][k-1]][k-1]\) 快速计算
    • 深度对齐:先将较深的节点提升到与较浅节点同一深度,保证两节点在同一层进行比较
    • 同步跳跃:从大到小尝试跳跃,若跳跃后两节点不同,则同时向上跳,直到 LCA 的下一层
    • 适用场景:静态树(树结构不变)、多次查询 LCA 的问题
    • 预处理一次,查询多次,适合询问次数较多的场景

【解题思路】

【算法标签】

最近公共祖先

【代码详解】

#include <bits/stdc++.h>  // 包含所有标准库头文件
using namespace std;const int N = 40005;  // 定义常量:N为节点数量上限
const int D = 16;  // 定义常量:D为最大深度(2^15足够覆盖大多数树的深度)
int n, m;  // n:节点数量;m:询问数量
vector<int> h[N];  // 邻接表存储树
int dep[N];  // dep[i]:节点i的深度
int fa[N][D];  // fa[x][i]:节点x向上走2^i步到达的节点
queue<int> q;  // 队列,用于BFS// BFS函数,预处理每个节点的深度和fa数组
void bfs(int root)
{memset(dep, 0x3f, sizeof(dep));  // 初始化深度为无穷大dep[0] = 0, dep[root] = 1;  // 虚拟节点0的深度为0,根节点的深度为1q.push(root);  // 将根节点加入队列while (!q.empty()) {  // 当队列不为空时int t = q.front();  // 取出队首节点q.pop();// 遍历t的所有邻接节点for (int i = 0; i < h[t].size(); i++) {int j = h[t][i];  // 邻接节点jif (dep[j] > dep[t] + 1) {  // 如果j的深度未被更新dep[j] = dep[t] + 1;  // 更新j的深度q.push(j);  // 将j加入队列fa[j][0] = t;  // j向上走2^0=1步到达t// 递推计算fa[j][k]for (int k = 1; k <= 15; k++) {fa[j][k] = fa[fa[j][k - 1]][k - 1];  // j向上走2^k等于j向上走2^(k-1)后再走2^(k-1)步}}}}
}// LCA函数,计算节点x和节点y的最近公共祖先
int lca(int x, int y)
{if (dep[x] < dep[y]) swap(x, y);  // 保证x为深度较大的节点// 步骤1:将x和y的深度调整一致for (int k = 15; k >= 0; k--) {if (dep[fa[x][k]] >= dep[y]) {  // 如果x向上走2^k步后深度仍大于等于yx = fa[x][k];  // x向上走2^k步}}if (x == y) return x;  // 如果x和y相等,说明x就是LCA// 步骤2:x和y同时向上跳,跳到LCA的下一层for (int k = 15; k >= 0; k--) {if (fa[x][k] != fa[y][k]) {  // 如果x和y向上走2^k步后不相等x = fa[x][k];  // x向上走2^k步y = fa[y][k];  // y向上走2^k步}}return fa[x][0];  // 返回x或y的父节点,即LCA
}int main()
{cin >> n;  // 输入节点数量nint root = 0;  // 根节点初始化为0// 输入树的边for (int i = 1; i <= n; i++) {int a, b;cin >> a >> b;  // 输入边的两个节点a和bif (b == -1) root = a;  // 如果b为-1,说明a是根节点else {h[a].push_back(b);  // 添加边a->bh[b].push_back(a);  // 添加边b->a(无向图)}}bfs(root);  // 预处理fa数组和dep数组cin >> m;  // 输入询问数量mwhile (m--) {int a, b;cin >> a >> b;  // 输入询问的两个节点a和bint p = lca(a, b);  // 计算a和b的LCAif (p == a) puts("1");  // 如果LCA是a,输出1else if (p == b) puts("2");  // 如果LCA是b,输出2else puts("0");  // 否则输出0}return 0;  // 程序结束
}

【运行结果】

10
234 -1
12 234
13 234
14 234
15 234
16 234
17 234
18 234
19 234
233 19
5
234 233
1
233 12
0
233 13
0
233 15
0
233 19
2
http://www.gsyq.cn/news/1534527.html

相关文章:

  • 一条金项链的回收日记:选合扬上门,资质透明没踩任何坑 - 开心测评
  • 实测武汉江岸区黄金回收商圈,这些机构值得看 - 上门黄金回收
  • 全国优质功率电感服务商推荐,布局广东广州等地区,德鸿感应打造高端国产电感智造标杆 - 十大品牌榜
  • 2026长沙上门收黄金,当场称重转账,正规机构无套路 - 逸程
  • Ollama本地部署实战:从安装加速到4B模型稳定运行
  • 2026鹤壁本地认可的 5 家排污许可废气废水监测机构实地测评汇总 废水废气 + 自行监测 + CMA 检测报告 附电话地址 - 科信检测
  • KNN算法原理与工程实践:从距离度量到百万级优化
  • 2026玉树当地贵金属回收权威名录 TOP5 黄金金条铂金白银回收线下门店信息汇总 - 信誉隆金银铂奢回收
  • 2026赤峰本地防雷检测哪家专业?TOP 正规机构榜单 + 防雷装置 + 接地电阻 + SPD 检测 附电话地址 - 中安检测集团
  • 2026新疆建筑工程材料检测 CMA 机构哪家强?TOP 正规检测中心榜单 + 电话地址 - 中检检测集团
  • 太原小店区商圈黄金回收实测:这些坑你踩过没 - 上门黄金回收
  • 时序回归实战:从CSV到上线预测的Python全流程
  • 伊犁全城贵金属回收优选门店 TOP5 黄金回收铂金回收白银回收正规商家地址汇总 - 中安检金银铂钻回收
  • 2026大连贵金属旧料回收优质实体店精选 5 家 黄金回收铂金白银回收真实探店测评清单 - 中业金奢再生回收中心
  • 全国优质共模电感专业厂家推荐,布局广东广州等地区,德鸿感应赋能高端电子产业更靠谱 - 十大品牌榜
  • Llama3、Qwen2、Mistral开源大模型选型实战指南
  • 2000-2024年地级市农业绿色全要素生产率GTFP
  • 2026金昌本地认可的 5 家排污许可废气废水监测机构实地测评汇总 废水废气 + 自行监测 + CMA 检测报告 附电话地址 - 科信检测
  • 轮胎撕碎机单机选型参考:从刀盘到产能的那些细节 - 深度智识库
  • 扩散语言模型原理与工程实践详解
  • R3nzSkin完整指南:5分钟掌握英雄联盟安全换肤技术
  • 对话式AI赛道全景:从大模型到智能体的范式跃迁与核心玩家解析
  • 子图匹配算法CEMR:优化NP难问题的计算效率
  • OpenClaw本地AI助理实战:基于Ollama的端到端消息层智能代理部署
  • iOS App性能测试工具的实现方法与优化循环指南
  • 模板驱动的文档操作系统:从内容到PDF的一键成型
  • NBA球员位置分类:仅用5项物理参数构建可解释模型
  • 徐州考 CPPM 多久能拿证? - 中供国培
  • Ray Ozzie协作哲学与Ray框架:构建离线优先、最终一致的分布式系统
  • 2026乌兰察布建筑工程材料检测 CMA 机构哪家强?TOP 正规检测中心榜单 + 电话地址 - 中检检测集团