Tree Jumps ( [Educational Codeforces Round 175 (Rated for Div. 2)] )
Tree Jumps ( Educational Codeforces Round 175 (Rated for Div. 2) )You are given a rooted tree, consisting ofnn vertices. The vertices in the tree are numbered from11 tonn, and the root is the vertex11
Tree Jumps ( Educational Codeforces Round 175 (Rated for Div. 2) )
You are given a rooted tree, consisting of n n n vertices. The vertices in the tree are numbered from 1 1 1 to n n n, and the root is the vertex 1 1 1. Let d x d_x dx be the distance (the number of edges on the shortest path) from the root to the vertex x x x.
There is a chip that is initially placed at the root. You can perform the following operation as many times as you want (possibly zero):
- move the chip from the current vertex v v v to a vertex u u u such that d u = d v + 1 d_u = d_v + 1 du=dv+1. If v v v is the root, you can choose any vertex u u u meeting this constraint; however, if v v v is not the root, u u u should not be a neighbor of v v v (there should be no edge connecting v v v and u u u).
For example, in the tree above, the following chip moves are possible: 1 → 2 1 \rightarrow 2 1→2, 1 → 5 1 \rightarrow 5 1→5, 2 → 7 2 \rightarrow 7 2→7, 5 → 3 5 \rightarrow 3 5→3, 5 → 4 5 \rightarrow 4 5→4, 3 → 6 3 \rightarrow 6 3→6, 7 → 6 7 \rightarrow 6 7→6.
A sequence of vertices is valid if you can move the chip in such a way that it visits all vertices from the sequence (and only them), in the order they are given in the sequence.
Your task is to calculate the number of valid vertex sequences. Since the answer might be large, print it modulo 998244353 998244353 998244353.
Input
The first line contains a single integer t t t ( 1 ≤ t ≤ 1 0 4 1 \le t \le 10^4 1≤t≤104) — the number of test cases.
The first line of each test case contains a single integer n n n ( 2 ≤ n ≤ 3 ⋅ 1 0 5 2 \le n \le 3 \cdot 10^5 2≤n≤3⋅105).
The second line contains n − 1 n-1 n−1 integers p 2 , p 3 , … , p n p_2, p_3, \dots, p_n p2,p3,…,pn ( 1 ≤ p i ≤ i 1 \le p_i \le i 1≤pi≤i), where p i p_i pi is the parent of the i i i-th vertex in the tree. Vertex 1 1 1 is the root.
Additional constraint on the input: the sum of n n n over all test cases doesn’t exceed 3 ⋅ 1 0 5 3 \cdot 10^5 3⋅105.
Output
For each test case, print a single integer — the number of valid vertex sequences, taken modulo 998244353 998244353 998244353.
Example
input
3
4
1 2 1
3
1 2
7
1 2 2 1 4 5
output
4
2
8
Note
In the first example, the following sequences are valid: [ 1 ] [1] [1], [ 1 , 2 ] [1, 2] [1,2], [ 1 , 4 ] [1, 4] [1,4], [ 1 , 4 , 3 ] [1, 4, 3] [1,4,3].
In the second example, the following sequences are valid: [ 1 ] [1] [1], [ 1 , 2 ] [1, 2] [1,2].
In the third example, the following sequences are valid: [ 1 ] [1] [1], [ 1 , 2 ] [1, 2] [1,2], [ 1 , 2 , 7 ] [1, 2, 7] [1,2,7], [ 1 , 2 , 7 , 6 ] [1, 2, 7, 6] [1,2,7,6], [ 1 , 5 ] [1, 5] [1,5], [ 1 , 5 , 3 ] [1, 5, 3] [1,5,3], [ 1 , 5 , 3 , 6 ] [1, 5, 3, 6] [1,5,3,6], [ 1 , 5 , 4 ] [1, 5, 4] [1,5,4].
题面描述
你有一个树,树中有 n
个节点,节点编号从 1
到 n
,根节点是节点 1
。定义 d x d_x dx 为从根节点到节点 x x x 的距离(即最短路径上的边数)。
有一个棋子最开始放在根节点。你可以执行以下操作若干次(可能为零次):
- 将棋子从当前节点 v v v 移动到一个距离为 d v + 1 d_v + 1 dv+1 的节点 u u u。如果 v v v 是根节点,则可以选择任何符合条件的节点;如果 v v v 不是根节点,则 u u u 必须不是 v v v 的邻居(即树中没有边连接 v v v 和 u u u)。
例如,在上面给出的树中,以下是有效的棋子移动:
- 1 → 2 1 \rightarrow 2 1→2, 1 → 5 1 \rightarrow 5 1→5
- 2 → 7 2 \rightarrow 7 2→7, 5 → 3 5 \rightarrow 3 5→3, 5 → 4 5 \rightarrow 4 5→4
- 3 → 6 3 \rightarrow 6 3→6, 7 → 6 7 \rightarrow 6 7→6
一段顶点序列是有效的,如果你能按给定的顺序移动棋子并访问所有的顶点,且只访问这些顶点。
你的任务是计算出有多少种有效的顶点序列。由于结果可能很大,请输出结果对 998244353 998244353 998244353 取模后的值。
输入
- 第一行输入一个整数 t t t,表示测试用例的数量。
- 对于每个测试用例:
- 第一行输入一个整数 n n n,表示树的节点数量。
- 第二行输入 n − 1 n-1 n−1 个整数 p 2 , p 3 , … , p n p_2, p_3, \dots, p_n p2,p3,…,pn,表示树中第 i i i 个节点的父节点为 p i p_i pi( 1 ≤ p i ≤ i 1 \le p_i \le i 1≤pi≤i)。树的根节点为节点 1 1 1。
输出
对于每个测试用例,输出一个整数,表示可能的有效顶点序列的数量,对 998244353 998244353 998244353 取模。
示例
输入:
3
4
1 2 1
3
1 2
7
1 2 2 1 4 5
输出:
4
2
8
解释
-
第一个测试用例:
树的结构为:1 / \ 2 5 / / \ 3 4 7 / 6
有效的顶点序列包括:
[1]
,[1, 2]
,[1, 4]
,[1, 4, 3]
。共计 4 种有效序列。 -
第二个测试用例:
树的结构为:1 / \ 2 3
有效的顶点序列包括:
[1]
,[1, 2]
。共计 2 种有效序列。 -
第三个测试用例:
树的结构为:1 / \ 2 5 / / \ 3 4 7 / 6
有效的顶点序列包括:
[1]
,[1, 2]
,[1, 2, 7]
,[1, 2, 7, 6]
,[1, 5]
,[1, 5, 3]
,[1, 5, 3, 6]
,[1, 5, 4]
。共计 8 种有效序列。
题解
我们可以使用动态规划来解决这个问题。设 d p v dp_v dpv 表示以顶点 v v v 结尾的有效序列的数量。
为了计算 d p v dp_v dpv,我们可以遍历序列中所有之前的顶点 u u u,使得 d v = d u + 1 d_v = d_u + 1 dv=du+1 且 u ≠ p v u \ne p_v u=pv(或者 u u u 是根节点)。那么, d p v dp_v dpv 就是所有满足条件的 u u u 对应的 d p u dp_u dpu 之和。
需要注意的是,顶点应该按照离根节点距离递增的顺序进行处理,因为否则,当我们在计算 d p v dp_v dpv 中需要用到 d p u dp_u dpu 时, d p u dp_u dpu 可能还未被计算出来。然而,这种方法可能会非常慢,因为对于每个顶点 v v v,可能会有 O ( n ) O(n) O(n) 个顶点 u u u。 为了加快这个解法的速度,我们可以观察到,如果顶点离根节点的距离相同,它们的 d p dp dp 值的计算遵循相似的模式。
具体来说,我们只需要考虑树中前一“层”顶点的 d p dp dp 值,同时排除它们的父节点。
因此,我们可以存储每一个“层”中所有 d p dp dp 值的总和。设 t o t i tot_i toti 是所有离根节点距离为 i i i 的顶点 v v v 对应的 d p v dp_v dpv 之和。那么我们可以很容易地将 d p v dp_v dpv 计算为 t o t d v − 1 − d p p v tot_{d_v - 1} - dp_{p_v} totdv−1−dppv(如果 p v p_v pv 是根节点,则减去 0 0 0)。该问题的答案就是所有 d p v dp_v dpv 值的总和。
为了根据顶点离根节点的距离对顶点进行排序,你可以使用深度优先搜索(DFS)(然后根据距离对顶点进行排序)或者广度优先搜索(BFS)。
或者你可以使用以下方法:由于对于每个顶点,其父节点的索引更小,我们可以按从 1 1 1 到 n n n 的顺序处理顶点,并设置 d v = d p v + 1 d_v = d_{p_v} + 1 dv=dpv+1。然后,对于每个深度,你可以构建一个包含该深度所有顶点的向量(将每个顶点添加到对应的向量中)。这种方法的时间复杂度为 O ( n ) O(n) O(n)。
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define BoBoowen ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
using namespace std;
const int mod = 998244353;
int n;
void solved()
{
cin >> n;
vector<int> g[n + 1];
vector<int> dep(n + 1);
vector<int> dp(n + 1);
vector<int> tot(n + 1);
// 构建树
for (int i = 1; i < n; ++i)
{
int x;
cin >> x;
x--;
g[x].push_back(i);
}
// 使用队列进行广度优先搜索,计算每个节点的dp值
queue<int> q;
q.push(0); // 从根节点开始
dp[0] = 1; // 根节点的序列数为1
while (!q.empty())
{
int now = q.front();
q.pop();
// 更新当前节点的序列数
dp[now] = (dp[now] + tot[dep[now]]) % mod;
// 更新该节点下一层的总和
tot[dep[now] + 1] = (tot[dep[now] + 1] + dp[now]) % mod;
// 继续处理所有子节点
for (auto it : g[now])
{
dep[it] = dep[now] + 1;
if (now != 0)
{
dp[it] = (mod - dp[now]) % mod; // 确保非根节点的序列数的正确性
}
q.push(it);
}
}
// 汇总结果
int ans = 0;
for (int i = 0; i <= n; ++i)
{
ans = (ans + dp[i]) % mod;
}
cout << ans << endl;
}
signed main()
{
BoBoowen;
int T = 1;
cin >> T;
while (T--)
{
solved();
}
}
代码解析:
- 树的构建:通过
g
数组存储树的邻接表。每个节点的父节点通过输入构建。 - dp 数组:
dp[i]
记录从根节点到节点i
的有效序列数。初始化时,根节点的序列数为1。 - tot 数组:
tot[i]
用来累加当前层次的有效序列数,以便在下一层的节点处理中使用。 - 广度优先搜索:通过队列进行广度优先搜索,逐层计算每个节点的有效序列数。
- 计算结果:最终通过累加所有节点的 dp 值得到最终结果。
时间复杂度:
- 对于每个测试用例,构建树的时间复杂度为 O ( n ) O(n) O(n),广度优先搜索的时间复杂度也是 O ( n ) O(n) O(n)。因此,每个测试用例的时间复杂度为 O ( n ) O(n) O(n),总的时间复杂度是 O ( T × n ) O(T \times n) O(T×n),其中 T T T 为测试用例的数量, n n n 为每个测试用例中的节点数。由于题目约束条件,能够在时间限制内通过。
结论
通过构建树和使用动态规划,结合广度优先搜索的方式,可以高效地计算出每个测试用例的有效顶点序列数。
更多推荐
所有评论(0)