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

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

把每一个基环抠出来。

大力分类讨论:

  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. gcc编译链接用到的环境变量

    PATH ----  可执行程序寻找路径 C_INCLUDE_PATH  ---- 头文件寻找路径 CPLUS_INCLUDE_PATH  --- g++ 头文件路径 LD_LIBRARY_PATH  ...

  2. rbd的image快照与Pool快照

    前言 这个问题是不久前在ceph社区群里看到的,创建image的时候,当时的报错如下: 2016-12-13 23:13:10.266865 7efbfb7fe700 -1 librbd::image ...

  3. linux系统克隆系统盘

    本文将介绍两种方式的系统盘的完整的备份,两种方式各有优缺点,需要根据实际情况来进行选择 使用dd的完整镜像克隆的方式 使用tar去备份数据,安装grub的方式 dd方式 优点: 简单,一条命令 dd ...

  4. Linux_CentOS 7下Nginx服务器的安装配置

    1.安装 1.1 配置epel yum 源 wget http://dl.Fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm rpm ...

  5. 07 . 前端工程化(ES6模块化和webpack打包)

    模块化规范 传统开发模式主要问题 /* 1. 命名冲突 2. 文件依赖 */ 通过模块化解决上述问题 /* 模块化就是把单独的一个功能封装在一个模块(文件)中,模块之间相互隔离, 但是可以通过特定的接 ...

  6. Spring Cloud实战 | 最八篇:Spring Cloud +Spring Security OAuth2+ Axios前后端分离模式下无感刷新实现JWT续期

    一. 前言 记得上一篇Spring Cloud的文章关于如何使JWT失效进行了理论结合代码实践的说明,想当然的以为那篇会是基于Spring Cloud统一认证架构系列的最终篇.但关于JWT另外还有一个 ...

  7. MySQL如何实现万亿级数据存储?

    前言 业界对系统的高可用有着基本的要求,简单的说,这些要求可以总结为如下所示. 系统架构中不存在单点问题. 可以最大限度的保障服务的可用性. 一般情况下系统的高可用可以用几个9来评估.所谓的几个9就是 ...

  8. C# Random类的正确应用

    Random类介绍 Random类一个用于产生伪随机数字的类.这里的伪随机表示有随机性但是可以基于算法模拟出随机规律. Random类的构造方式有两种. Random r= new Random(). ...

  9. 使用Camtasia创作抖音卡点视频

    空闲的时候刷一刷抖音相信已经成为很多人的日常啦,抖音里面的视频形式多种多样,而其中的卡点视频更是被大家热烈追捧.如果你外出旅行拍摄了很多好看的照片,就很适合用卡点视频的形式展现出来. 如果你想要制作这 ...

  10. 自定义IDM的网页嗅探下载浮条样式

    如果大家有用过IDM(Internet Download Manager)下载器的朋友应该会知道,我们在安装完IDM后,打开网页时,有时网页上会出现一个IDM的下载浮窗,这就是IDM的嗅探下载浮条. ...