视频加载失败

安吉D11-A

1663 字
8 分钟
安吉D11-A
原题呈现

P6374 树上询问#

题目描述#

给定一棵 nn 个点的无根树,有 qq 次询问.

每次询问给一个参数三元组 (a,b,c)(a,b,c),求有多少个 ii 满足这棵树在以 ii 为根的情况下 aabb 的最近公共祖先为 cc

输入格式#

第一行 22 个数,为 nnqq

接下来 n1n-1 行,每行 22 个数,表示树的一条边.

接下来 qq 行,每行 33 个数,为 (a,b,c)(a,b,c)

输出格式#

qq 行,每行一个数,为对于每个三元组的 ii 的个数.

输入输出样例 #1#

输入 #1#

10 5
1 2
1 3
2 4
2 5
2 10
5 6
3 7
7 8
7 9
4 6 2
4 10 1
6 8 3
9 10 2
4 10 5

输出 #1#

7
0
1
4
0

输入输出样例 #2#

输入 #2#

5 3
1 3
1 5
3 4
3 2
5 2 3
5 2 1
2 4 5

输出 #2#

2
1
0

输入输出样例 #3#

输入 #3#

20 10
1 2
1 3
1 4
2 5
2 6
3 10
4 13
4 14
6 7
6 8
10 11
4 15
4 16
8 9
11 12
16 17
16 18
16 19
17 20
15 19 16
1 12 1
20 20 20
7 7 8
1 8 3
5 20 2
2 9 6
9 12 1
9 12 2
9 12 3

输出 #3#

4
16
20
0
0
5
2
10
2
1

说明/提示#


样例 2 解释#

第一个查询的 ii3344

第二个查询的 ii11


数据范围#

本题按子任务测试:#

  • Subtask 1(2020 pts):1n10001 \leq n \leq 10001q5001 \leq q \leq 500

  • Subtask 2(1515 pts):1n1051 \leq n \leq 10^{5}1q1051 \leq q \leq 10^{5},树退化成链.

  • Subtask 3(2525 pts):1n5×1051 \leq n \leq 5 \times 10^{5}1q1051 \leq q \leq 10^{5},数据不随机

  • Subtask 4(4040 pts):1n5×1 \leq n \leq 5 \times 10510^{5}1q2×1051 \leq q \leq 2 \times 10^{5}

对于所有数据:1n5×1051 \leq n \leq 5 \times 10^{5}1q2×1051 \leq q \leq 2 \times 10^{5}

注:数据强度不高,不必卡常与快读快输.

考虑样例 2,当询问为 (2,5,z)(2, 5, z) 时,此时,

根节点lca(2,5)
11
22
33
43
55

显然,不在 252\to 5 这条链上的 44 不可能为两节点的最近公共祖先.实际上,对于询问 (x,y,z)(x,y,z),不在 xyx\to y 这条链上的所有点 aa,若满足 a=za=z,则方案数必然为 00.因为若 aaxyx\to y 连通时存在一个汇入点 bb,满足 aba\to bxyx\to y 交于点 bb,且点 bbxyx\to y 上,则此时路径 axa\to xaya\to y 会有重合(aba\to b).因此,aa 不能成为 x,yx,y 的最近公共祖先.

那对于 zzxyx\to y 上的情况怎么办呢?那么此时首先,zz 自身可以作为根节点.其次,不在 xyx\to y 上的点,且与 zz 连通的点可以将 zz 拎起来.因此,答案就是不在 xyx\to y 上且与 zz 直接相连(这里指不通过 xyx\to y 上的任何边就能够相连)的点以及 zz 本身的节点数量.

考虑如何 O(1)O(1) 算出.显然,我们可以以 11 为根建树进行一次 dfs,那么,对于询问 (x,y,z)(x,y,z),会有四种可能:

  • zz 不在 xyx\to y 上.此时根据分析,输出 0.
  • zz 正好是 x,yx,y 的最近公共祖先.此时,所有可能的根节点就是 zz 去除含有 x,yx,y 的两颗子树(这里含有……的子树指所有以 zz 子节点为根节点的子树中含有该节点的子树,下同)大小,剩余的图中所有点都可以作根
  • zzx,yx,y 的最近公共祖先的含有 xx 的子树上,如上述样例中的 (2,5,3)(2,5,3)33 就在 2,52,5 最近公共祖先 11 的含有 22 的子树上.再如下图中的 (x,y,z)(x,y,z),那么此时,zz 的子树中含有 xx 的子树部分(即图中节点 u,x,D,Eu,x,D,E)肯定不行(因为子树上的任意一点都需要经过 uzu\to z,这条边在 xyx\to y 上),且此时除了 zz 子树之外的部分 ss (即图中点 A,b,y,vA,b,y,v)也不能作为子节点(因为 ss 上的每一个点想要到达 zz 都需要经过 vzv\to z,显然 yy 不在 zz 的子树上,故 yzxy\to z\to x 也需要通过 vzv\to z,因此 vzv\to zxyx\to y 上)因此答案为:所有的节点,减去 zz 子树中含有 xx 的子树大小,再减去不在 zz 子树中的节点.
  • zzx,yx,y 的最近公共祖先的有 yy 的子树上,这一种和上一种相同.

x

y

z

u

v

A

B

C

D

E

x

y

z

u

v

A

B

C

D

E

小技巧:如何 O(1)O(1) 计算一个点是否是另外一个点的子孙节点.

这需要用到 dfs 序,所谓 dfs 序,就是通过 dfs 首次先序遍历到每一个节点的次序,这可以在 dfs 的时候预处理出来.节点 uu 的 dfs 序常记为 dfnu\operatorname{dfn}_u. 不难发现,同一个子树内的节点,dfndfn 是连续的.可以利用这个性质,若我们现在想要知道 vv 是否是 uu 的子节点,那么这个命题完全等同于:

\DeclareMathOperator\dfndfn\dfnu\dfnv<\dfnu+size\DeclareMathOperator{\dfn}{dfn} \dfn_u \leq \dfn_v \lt \dfn_u + size

,其中,sizesizeuu 及其子节点的数量.

可以写出代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 100;
int n, q;
vector<int> e[N];
int f[22][N], cnt, dfn[N], sz[N], dep[N];
int LCA(int x, int y) {
if (dep[x] > dep[y]) {
swap(x, y);
}
int step = dep[y] - dep[x];
for (int i = 20; i >= 0; i--) {
if (step & (1 << i)) {
y = f[i][y];
}
}
if (x == y)
return x;
for (int i = 20; i >= 0; i--) {
if (f[i][x] != f[i][y]) {
x = f[i][x];
y = f[i][y];
}
}
return f[0][x];
}
void dfs(int t, int fa) {
f[0][t] = fa;
dep[t] = dep[f[0][t]] + 1;
dfn[t] = ++cnt;
for (int i = 0; i < e[t].size(); i++) {
if (e[t][i] != fa)
dfs(e[t][i], t);
}
for (int i = 0; i < e[t].size(); i++) {
if (e[t][i] != fa)
sz[t] += sz[e[t][i]];
}
sz[t]++;
}
/**
* 判断 x 是不是在 y 的子树中
*/
bool isIn(int x, int y) { return dfn[y] <= dfn[x] && dfn[x] < dfn[y] + sz[y]; }
/**
* 通过倍增计算以fa子节点为根节点的子树中含有dir的子树
*/
int getSubSz(int dir, int fa) {
if (dir == fa) {
return 0;
}
for (int i = 20; i >= 0; i--) {
int now = f[i][dir];
if (dfn[now] > dfn[fa]) {
dir = now;
}
}
return sz[dir];
}
signed main() {
cin >> n >> q;
for (int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs(1, 0);
for (int j = 1; j <= 20; j++) {
for (int i = 1; i <= n; i++) {
f[j][i] = f[j - 1][f[j - 1][i]];
}
}
for (int i = 1; i <= q; i++) {
int a, b, c;
cin >> a >> b >> c;
if (LCA(a, b) == c) {
cout << n - getSubSz(a, c) - getSubSz(b, c) << endl;
} else if (isIn(a, c) && !isIn(b, c)) {
cout << n - getSubSz(a, c) - (n - sz[c]) << endl;
} else if (!isIn(a, c) && isIn(b, c)) {
cout << n - getSubSz(b, c) - (n - sz[c]) << endl;
} else {
cout << 0 << endl;
}
}
return 0;
}

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

安吉D11-A
https://blog.jerrylab.top/posts/problem/anji2026/D11-A/
作者
Jerry
发布于
2026-08-11
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Jerry
Hello, I'm Jerry.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
80
分类
3
标签
35
总字数
89,198
运行时长
0
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0