一个思路不难,但是实现起来有点毒瘤的题。

显然题目给出的是基环树(内向树)森林。

把每一个基环抠出来。

大力分类讨论:

  1. 若 \(a, b\) 不在一个联通量里,显然是 \(-1, -1\)
  2. 若 \(a, b\) 在同一颗子树内,他们聚合的点显然是最近公共祖先,因为如果再往上走,2的条件就不满足。
  3. 若 \(a, b\) 在同一联通量(同一颗基环树),两颗不同的子树内。显然他们必须走到树根,然后这时有两个选择:从 \(root[a]\) 走到 \(root[b]\),或反之。我们求出两种走的步数,按照题意进行比较即可。

第一次遇到内向树,找基环的时候不知道该如何搞。(偷瞄了一眼别人的代码)发现他是自底向上进行递归,跟往常我们写的树的自顶向下相反,学习了一下。

相比来说自底向上貌似对内向树代码简化很多(只需一次dfs),原因在于:

  1. 若自顶向下遍历,我们找到一个环的时候,可能他的子树中的某些点已经遍历过了,也就是说 \(dep\)、\(root\) 等数组都算的不对,可能需要二次 \(dfs\)。
  2. 若自底向上遍历,只有当找到环之后,一个点才会确保更新。
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
const int N = 500005, L = 19;
int n, Q, fa[N][L], f[N], root[N], dep[N], sum[N], ult[N];
bool vis[N], ins[N];
int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); }
void dfs(int u) {
vis[u] = ins[u] = true;
int v = fa[u][0];
if (ins[v]) {
// 此时从 v -> fa[v][0], ->..., -> u -> v... 构成一个环
int cnt = 0, y = v;
while (1) {
root[y] = y, dep[y] = 0, ult[y] = u;
sum[y] = ++cnt, ins[y] = false;
if (y == u) break;
y = fa[y][0];
}
return;
}
if (!vis[v]) dfs(v);
if (!root[u]) root[u] = root[v], dep[u] = dep[v] + 1, ins[u] = false;
for (int i = 1; i < L && fa[u][i - 1]; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1];
}
int lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
for (int i = L - 1; ~i; i--)
if (dep[x] - (1 << i) >= dep[y]) x = fa[x][i];
if (x == y) return x;
for (int i = L - 1; ~i; i--)
if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
int main() {
scanf("%d%d", &n, &Q);
for (int i = 1; i <= n; i++) f[i] = i;
for (int i = 1; i <= n; i++) scanf("%d", &fa[i][0]), f[find(i)] = find(fa[i][0]);
for (int i = 1; i <= n; i++) if (!vis[i]) dfs(i);
while(Q--) {
int a, b; scanf("%d%d", &a, &b);
if (find(a) != find(b)) puts("-1 -1");
else if(root[a] == root[b]) {
int p = lca(a, b);
printf("%d %d\n", dep[a] - dep[p], dep[b] - dep[p]);
} else {
int x = dep[a], y = dep[b], cx, cy;
if (sum[root[a]] < sum[root[b]]) cx = sum[root[b]] - sum[root[a]], cy = sum[ult[root[a]]] - cx;
else cy = sum[root[a]] - sum[root[b]], cx = sum[ult[root[a]]] - cy;
if (max(x + cx, y) < max(x, y + cy) || (max(x + cx, y) == max(x, y + cy) && (min(x + cx, y) < min(x, y + cy) || (min(x + cx, y) == min(x, y + cy) && x + cx >= y))))
printf("%d %d\n", x + cx, y);
else printf("%d %d\n", x, y + cy);
}
}
return 0;
}

AcWing 392. 会合的更多相关文章

  1. 392. Is Subsequence

    392. Is Subsequence 水题,先是判断长度,长度t比s小,返回false,然后从左到右扫描t,然后同时扫描s,如果相同,s的index就往后拉一个,如果s的index等于s长度,返回t ...

  2. 腾讯机试题 AcWing 603 打怪兽

    题目链接:https://www.acwing.com/problem/content/605/ 题目大意: 略 分析: 用dp[i][j]表示用j元钱能在前i只怪兽上所能贿赂到的最大武力值. 有一种 ...

  3. AcWing 143. 最大异或对

    https://www.acwing.com/problem/content/145 #include <iostream> #include <algorithm> usin ...

  4. AcWing 153. 双栈排序

    https://www.acwing.com/problem/content/155/ #include <cstring> #include <iostream> #incl ...

  5. acwing 3 完全背包

    习题地址 https://www.acwing.com/problem/content/description/3/ 题目描述有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用. 第 i ...

  6. AcWing 164. 可达性统计

    给定一张N个点M条边的有向无环图,分别统计从每个点出发能够到达的点的数量. 输入格式 第一行两个整数N,M,接下来M行每行两个整数x,y,表示从x到y的一条有向边. 输出格式 输出共N行,表示每个点能 ...

  7. AcWing 291.蒙德里安的梦想

    题目:蒙德里安的梦想 链接:(蒙德里安的梦想)[https://www.acwing.com/problem/content/293/] 题意:求把N * M的棋盘分割成若干个1 * 2的长方形,有多 ...

  8. acwing 861. 二分图的最大匹配 模板

    地址  https://www.acwing.com/problem/content/description/863/ 给定一个二分图,其中左半部包含n1n1个点(编号1~n1n1),右半部包含n2n ...

  9. acwing 851. spfa求最短路 模板

    地址 https://www.acwing.com/problem/content/description/853/ 给定一个n个点m条边的有向图,图中可能存在重边和自环, 边权可能为负数. 请你求出 ...

随机推荐

  1. 224、Basic Calculator

    Implement a basic calculator to evaluate a simple expression string. The expression string may conta ...

  2. umask及文件默认和原始权限说明

    umask作用:设置了用户创建文件的默认权限.是权限的补码,一般在/etc/profile.$ [HOME]/.bash_profile或$[HOME]/.profile中设置umask值. 查看um ...

  3. 阿里巴巴已offer:Java实习五面详细面经(附解答)

    1.岗位 Java后台开发实习生 2.时间表 2020/3/18 提交简历 & 测评 2020/3/23 笔试 2020/3/26 简历面 2020/4/11 技术一面 2020/4/14 技 ...

  4. 分布式监控系统之Zabbix宏、模板和自定义item

    前文我们聊了下zabbix的基础使用,包括主机的添加.监控项.触发器.action以及告警通知的配置,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/140073 ...

  5. Pinpoint 编译环境搭建(Pinpoint系列一)

    本文基于 Pinpoint 2.1.0 版本 目录 一.2.1.0 版本特性 二.编译环境准备 三.编译注意事项 四.编译目录 五.注意事项 新版本的内容参考官方文档, Pinpoint的整个搭建是历 ...

  6. php递归无限查询上级或者下级

    $this->get_array($user['uid'],1); function get_array($user_id,$top=0){ $sql = 'SELECT * FROM ' . ...

  7. 防sql注入函数

  8. 有什么OCR文字识别软件好用?

    OCR文字识别是指:对文本资料进行扫描,然后对图像文件进行分析处理,最后获取文字以及版面信息的过程.对于许多学生党而言,一款好用的文字识别软件,能节省很多抄笔记的时间,而对于许多处理文字内容的白领而言 ...

  9. [教程] Android Native内存泄漏检测方法

    转载请注明出处:https://www.cnblogs.com/zzcperf/p/9563389.html Android 检测 C/C++内存泄漏的方法越来越简便了,下面列举一下不同场景下检测C/ ...

  10. 【mq学习笔记】mq 过期文件删除机制

    broker不会关注这个文件上的消息是否全部被消费.默认每个文件的过期时间为72小时.