2025.7.19 (题解:D. Tree Jumps)
刷题日记
今天这场Div. 1 + Div. 2真史,出的什么垃圾题啊wk
但是这和我今天下午的写题写爽了又有什么关系呢
一步步从无到有,循序渐进,最后推出结论的感觉,谁懂啊
Educational Codeforces Round 175 (Rated for Div. 2) D. Tree Jumps
https://codeforces.com/contest/2070/problem/D
给定一棵树,现定义dx是点x到根节点的最短距离
如果要从点u移动到点v,需要满足两个条件:
(除了根节点到自己的子节点,其余情况均需满足以下两个条件)
- dv = du + 1
- u和v不是邻居
请计算出有效顶点序列的数量,结果对998244353取模
注:这里的“顶点序列”为,从根节点走到节点x的序列(可能有很多种)
在移动过程中,从u移动到v,需满足不相邻且dv = du + 1
那么首先我们可以想到的是,用bfs去预处理每一层的结点数,然后到时候依次加起来?
但是很快我们意识到,能到达节点v的节点u肯定不止一个
而v节点能通向的节点也不止一个,那么这个时候产生的序列数应该是相乘而非简单的相加
到这里,感觉有点像树上dp,但我们可以先用bfs预处理一下
我们用dp[i]表示走到i节点的方案数,那么最后求出∑dp[i] % mod (i ∈ [1, n]) 就是答案了
那么dp[i]该怎么推呢?
我们不难发现,走到i节点的方案数,其实就是所有走到i的上个节点的方案数之和
因此dp[v] = ∑dp[u] (du + 1 = dv)
为了递推这个,我们需要开一个数组cnt,记录每一层有哪些节点
点击查看代码
void solve () {
int n;
std::cin >> n;
std::vector<i64> fa(n + 1), g[n + 1], dp(n + 1);
std::vector<pii> cnt[n + 1];
for (int i = 2; i <= n; i++) {
std::cin >> fa[i];
g[fa[i]].push_back(i);
}
std::queue<pii> q;
q.push({1, 1});
while (q.size()) {
auto [now, step] = q.front();
q.pop();
cnt[step].push_back({now, cnt[step - 1].size() - 1});
for (auto next : g[now]) {
q.push({next, step + 1});
}
}
i64 ans = 0;
q.push({1, 1});
while (q.size()) {
auto [now, step] = q.front();
q.pop();
if (step > 2) {
for (auto [last, num] : cnt[step - 1]) {
if (last != fa[now]) {
dp[now] = (dp[now] + dp[last]) % mod;
}
}
} else {
dp[now] = 1;
}
for (auto next : g[now]) {
q.push({next, step + 1});
}
}
for (int i = 1; i <= n; i++) {
ans = (ans + dp[i]) % mod;
}
std::cout << ans << '\n';
}
好的,超时了,Time limit exceeded on test 6
都能跑到test6,至少说明我们思路没问题
观察代码,显然我们的问题出现在了这里
if (step > 2) {
for (auto [last, num] : cnt[step - 1]) {
if (last != fa[now]) {
dp[now] = (dp[now] + dp[last]) % mod;
}
}
}
这段代码是我们超时的主要原因,我们对于树上的所有点
都遍历了一遍他的子节点,这样的话时间复杂度会到达O(n ^ 2)
那么我们能采取什么优化呢?
此时我们可以注意到,这里进行的过程是“求和”
所以我们的目的是在O(1)内获取到当前节点的上一层节点dp值之和 - 当前节点父节点的dp值
可以考虑前缀和,代码表示为sum[last_step] = sum[last_step] + dp[last_dot];
但是我们此时注意到,前缀和的计算,需要当前层每个节点的dp值才可以
但我们一开始没有dp值,dp值只能一层一层推出来,这该怎么办?
其实不难,我们继续用广搜的方式去递推每一层每一个节点的dp值
如果推完了第i - 1层,那么我们在刚刚进入到第i层时去计算第i - 1层的前缀和
这样在递推第i层的节点的dp值时,就可以直接调用i - 1层的前缀和了!
这样算下来,每个节点被访问两遍,复杂度是O(2 * n),能通过题目
点击查看代码
void solve () {
int n;
std::cin >> n;
std::vector<i64> fa(n + 10), g[n + 10], cnt[n + 10];
for (int i = 2; i <= n; i++) {
std::cin >> fa[i];
g[fa[i]].push_back(i);
}
std::queue<pii> q;
q.push({1, 1});
while (q.size()) {
auto [now, step] = q.front();
q.pop();
cnt[step].push_back(now);
for (auto next : g[now]) {
q.push({next, step + 1});
}
}
std::vector<i64> dp(n + 10), sum(n + 10);
sum[1] = 1;
for (int i = 1; i <= n; i++) {
if (fa[i] == 1 || i == 1) {
dp[i] = 1;
}
}
q.push({1, 1});
int last_step = 0;
while (q.size()) {
auto [now, step] = q.front();
q.pop();
if (step > last_step) {
for (auto last_dot : cnt[last_step]) {
sum[last_step] = sum[last_step] + dp[last_dot];
}
last_step = step;
}
if (step > 2) {
dp[now] = (dp[now] + sum[step - 1] - dp[fa[now]]) % mod;
} else {
dp[now] = 1;
}
for (auto next : g[now]) {
q.push({next, step + 1});
}
}
i64 ans = 0;
for (int i = 1; i <= n; i++) {
ans = (ans + dp[i]) % mod;
}
std::cout << ans << '\n';
}
这个代码多少有点丑陋了,优化一下
我们可以注意到,记录当前层有哪些节点的cnt数组
其实可以在递推dp的时候去更新,这样我们只需要跑一遍bfs
然后我们还有一个非常重要的关键点
就是sum[last_step] = sum[last_step] + dp[last_dot];这里不能取模!
因为我们后续在计算时有dp[now] = (dp[now] + sum[step - 1] - dp[fa[now]]) % mod;这个操作
而如果我们前面取模了,算出来的sum就是不准确的,这里直接减去dp[fa[now]],算出来的结果就错了
通俗的讲,我们要的答案是
dp[now] = (dp[now] + sum[step - 1] - dp[fa[now]]) % mod
而不是
dp[now] = (dp[now] + (sum[step - 1] % mod) - dp[fa[now]]) % mod
在经过优化后,给出的最终代码如下:
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define debug(x) std::cout << #x << " = " << x << '\n'
#define debugp(x, y) std::cout << #x << ", " << #y << " = " << "[" << x << ", " << y << "]\n"
#define debugt(x, y, z) std::cout << #x << ", " << #y << ", " << #z << " = " << "[" << x << ", " << y << ", " << z << "]\n"
#define debug1(f, a, b) std::cout << #f << ": "; for (int i = (a); i <= (b); i++) std::cout << (f)[i] << " \n"[i == (b)]
#define debug2(f, a, b, c, d) std::cout << #f << ":\n"; for (int i = (a); i <= (b); i++) for (int j = (c); j <= (d); j++) std::cout << (f)[i][j] << " \n"[j == (d)]
#define debug_1(q) std::cout << #q << ": "; for (auto it : (q)) std::cout << it << ' '; std::cout << '\n'
#define debug_2(q) std::cout << #q << ":\n"; for (auto [x, y] : (q)) std::cout << '[' << x << ", " << y << "]\n"
#define debug_3(q) std::cout << #q << ":\n"; for (auto [x, y, z] : (q)) std::cout << '[' << x << ", " << y << ", " << z << "]\n"
#define show_pq(q) std::cout << #q << ": "; while ((q).size()) { std::cout << (q).top() << ' '; (q).pop(); } std::cout << '\n'
#define show_gh(g) std::cout << #g << ":\n"; for (int i = 1; i <= n; i++) { std::cout << i << ": "; for (auto it : (g)[i]) std::cout << it << ' '; std::cout << '\n'; }
using i64 = long long;
using pii = std::pair<int, int>;
using namespace __gnu_pbds;
using ordered_set = tree<i64, null_type, std::less<i64>, rb_tree_tag, tree_order_statistics_node_update>;
using ordered_multiset = tree<i64, null_type, std::less_equal<i64>, rb_tree_tag, tree_order_statistics_node_update>;
constexpr int N = 2e5 + 10;
constexpr int inf = INT_MAX;
constexpr i64 INF = LLONG_MAX;
constexpr int mod = 998244353;
/*====================My_Solution====================//
在移动过程中,从u移动到v,需满足不相邻且dv = du + 1
那么我们可以用bfs去预处理每一层的结点数,然后到时候依次相乘?
虽然感觉有点像树上dp,但还是先用bfs试一下
dp数组在计算时超时了,显然是因为我们遍历了上一层,不够高效
这时我们可以用前缀和优化
但是,计算区间和也是需要知道dp数组才可以,只能通过dp数组递推
我们可以在dp数组计算完当前层之后再计算sum数组
这样下一层计算dp时可以直接调用上一层的sum数组
这样每个点只会被访问两遍,总复杂度O(2 * n)
//====================My_Solution====================*/
void solve () {
int n;
std::cin >> n;
std::vector<i64> fa(n + 10), g[n + 10], cnt[n + 10];
std::vector<i64> dp(n + 10), sum(n + 10);
sum[1] = 1;
for (int i = 2; i <= n; i++) {
std::cin >> fa[i];
g[fa[i]].push_back(i);
}
for (int i = 1; i <= n; i++) {
if (fa[i] == 1 || i == 1) {
dp[i] = 1;
}
}
std::queue<pii> q;
q.push({1, 1});
int last_step = 0;
while (q.size()) {
auto [now, step] = q.front();
q.pop();
cnt[step].push_back(now);
if (step > last_step) {
for (auto last_dot : cnt[last_step]) {
sum[last_step] = sum[last_step] + dp[last_dot]; // 这里不能对mod取模!!
}
last_step = step;
}
if (step > 2) {
dp[now] = (dp[now] + sum[step - 1] - dp[fa[now]]) % mod;
}
for (auto next : g[now]) {
q.push({next, step + 1});
}
}
i64 ans = 0;
for (int i = 1; i <= n; i++) {
ans = (ans + dp[i]) % mod;
}
std::cout << ans << '\n';
}
int32_t main () {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t = 1;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}
感谢观看

浙公网安备 33010602011771号