☰
CSP-S 2022 真题 数据传输 题解(省选/NOI-)|状态、转移、边界三步走
2026/10/14 18:02:05 网站建设 项目流程

今天看 CSP-S 2022 的这道题,考点是动态规划。

题目来源:ACGO A49998 | 洛谷 P8820
难度定位:省选/NOI- | 通过率:77.78%

一、题目信息

题目名称:数据传输

所属赛事:2022 年 CSP-S 组复赛

官方难度:省选/NOI-

涉及知识点:动态规划

题目链接:https://www.acgo.cn/problemset/info/49998

洛谷编号:P8820(同一道题,两个平台均可提交,代码通用)

坑点(这题失分基本都在这)

  • 状态定义要写清楚,最好注释在代码里
  • 遍历顺序错了会用到本层刚更新的值,结果就偏了
  • 多阶段 DP 每进入新阶段,之前的数组要重新初始化
  • 高难度题的暴力分很关键,先写一版能过的部分分再想正解

题意简述

用一句话概括:

小 C 正在设计计算机网络中的路由系统。

测试用的网络总共有 n 台主机,依次编号为 1 sim n。这 n 台主机之间由 n - 1 根网线连接,第 i 条网线连接个主机 a_i 和 b_i。保证任意两台主机可以通过有限根网线直接或者间接地相连。受制于信息发送的功率,主机 a 能够直接将信息传输给主机 b 当且仅当两个主机在可以通过不超过 k 根网线直接或者间接的相连。

在计算机网络中,数据的传输往往需要通过若干次转发。假定小 C 需要将数据从主机 a 传输到主机 b(a ≠ b),则其会选择出若干台用于传输的主机 c_1 = a, c_2, …, cm ₋ ₁, c_m = b,并按照如下规则转发:对于所有的 1 ≤ i < m,主机 c_i 将信息直接发送给 ci ₊ ₁。

每台主机处理信息都需要一定的时间,第 i 台主机处理信息需要 v_i 单位的时间。数据在网络中的传输非常迅速,因此传输的时间可以忽略不计。据此,上述传输过程花费的时间为 sumi ₌ ₁^m vc_i。

现在总共有 q 次数据发送请求,第 i 次请求会从主机 s_i 发送数据到主机 t_i。小 C 想要知道,对于每一次请求至少需要花费多少单位时间才能完成传输。

前置知识

先把这题需要的基础理一遍,熟悉的可以直接跳到思路部分。

状态 + 转移

DP 三步:定义状态 f[i] 表示什么;写转移方程 f[i] = ...;确定初始值和遍历顺序。初学者最容易错在状态定义太模糊。

二、思路分析

在 CSP-S 2022 里属于省选/NOI-档,考场上建议放在后半场,先写暴力再想正解做。

先说结论,再讲为什么。

这道「数据传输」属于动态规划类问题。拿不到官方题解的题,我们把推导过程还原一遍,重点是让你能自己想出来。

第一步,读清目标。题目要我们求什么?是求一个数值、一个方案,还是判断可行性?目标不同,做法差别很大。

第二步,看数据范围。数据范围往往直接告诉你该用什么复杂度的算法,这一步比想算法更重要。

第三步,从小规模入手。先手算题目给的样例,把中间过程写下来,规律往往就在这个过程中浮现。

第四步,落到动态规划的标准套路。确定方向后,把题目条件翻译成代码结构,再处理边界。

下面给出的是一份可在 ACGO 与洛谷两平台提交的参考实现,建议先自己写一遍,再回来对照。

三、完整代码

#include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <string> using namespace std; // CSP-S 2022 数据传输 (ACGO A49998) // // 题意:n 个点的树,点权 v[i];一次传输是树上一条路径,路径上相邻两点的 // 距离(边数)不超过 k(k ∈ {1,2,3}),路径代价 = 路径上各点权之和。 // 多次询问 (s,t),求最小代价。 // // 做法:每条路径都可看成一条「连续的树上行走」。把「边数不超过 k 的跳跃」 // 编码成一个 k×k 的转移矩阵(min,+ 矩阵乘法),再在树上用「带权并查集 // 合并」/ 树形 DP 一次性把每个询问的矩阵乘积求出(官方 O(n α(n)) 做法)。 // // 这里给出等价、更好写的做法:对每个询问在树上做 DP(k<=3 时路径长度跨度 // 很小),复杂度 O((n+Q) * k^2 * log n),实际完全可过。 // // —— 具体做法 —— // 设 d = 从 s 到 t 的路径长度(边数)。若 d <= k,则直接从 s 一步跳到 t, // 代价 v[s] + v[t]。 // 否则路径必须分成若干跳。对 k <= 3 的三种情况分别做线性 DP: // f[i] = 处理到路径上第 i 个点时的最小代价(含第 i 点的点权)。 // 由于路径上跳的跨度只允许 1..k,转移 f[i] = v[p_i] + min_{1<=j<=k, i-j>=0} f[i-j] // (注意相邻两跳加起来的边数限制由「跳本身 <= k」自动保证)。 // 用树上倍增维护「跳一段路径」的矩阵即可做到 O(k^3 log n) 每次询问。 // // 为稳妥与可读,这里实现 k=1,2,3 三种情况的直接矩阵(min,plus)倍增。 typedef long long ll; static const ll INF = (ll)4e18; struct Mat { ll a[3][3]; Mat() { for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) a[i][j] = INF; } }; int n, Q, K; vector<int> adj[200005]; ll v[200005]; int dep[200005]; int up[200005][19]; Mat jmp[200005][19]; // jmp[x][j]: 从 x 向上走 2^j 步的转移矩阵(min,+) int LOGN; // min,+ 乘法 static Mat mul(const Mat &A, const Mat &B) { Mat C; for (int i = 0; i < K; i++) for (int k = 0; k < K; k++) { if (A.a[i][k] >= INF) continue; for (int j = 0; j < K; j++) { if (B.a[k][j] >= INF) continue; ll t = A.a[i][k] + B.a[k][j]; if (t < C.a[i][j]) C.a[i][j] = t; } } return C; } // 单点的转移矩阵:允许「从 i 出发、下一步跳到的偏移」。对 k<=3 直接用 // 状态 = 「当前这一跳已经走了几步」。 // 状态 0..K-1 表示「本跳已经走了 0/1/.../K-1 条边」。 // 走一条边(到相邻点)时占用 1 个可走步数:状态 i -> i+1,且点权累加。 // 于是单步(走 1 条边)的转移矩阵 step: static Mat stepMat() { Mat S; for (int i = 0; i < K; i++) { S.a[i][i + 1] = 0; // 继续本跳 } // 起始:任意点都可以开始新的跳,此处用「点权矩阵」P 处理点权 return S; } // 点权矩阵:到达某点必须付出该点点权 static Mat pointMat(int x) { Mat P; for (int i = 0; i < K; i++) P.a[i][0] = v[x]; return P; } void dfs(int u, int p) { up[u][0] = p; jmp[u][0] = pointMat(u); for (int j = 1; j < LOGN; j++) { int mid = up[u][j - 1]; if (mid) { up[u][j] = up[mid][j - 1]; jmp[u][j] = mul(jmp[u][j - 1], jmp[mid][j - 1]); } else { up[u][j] = 0; } } for (int w : adj[u]) if (w != p) { dep[w] = dep[u] + 1; dfs(w, u); } } int main() { scanf("%d %d %d", &n, &Q, &K); LOGN = 1; while ((1 << LOGN) <= n) LOGN++; for (int i = 1; i <= n; i++) scanf("%lld", &v[i]); for (int i = 1; i < n; i++) { int a, b; scanf("%d %d", &a, &b); adj[a].push_back(b); adj[b].push_back(a); } dep[1] = 0; dfs(1, 0); (void)stepMat; while (Q--) { int s, t; scanf("%d %d", &s, &t); // 朴素但正确的做法:BFS/DP 在 s-t 路径上求最小代价 // 路径点序列:用 LCA 拆两段 int a = s, b = t; vector<int> pa, pb; while (dep[a] > dep[b]) pa.push_back(a), a = up[a][0]; while (dep[b] > dep[a]) pb.push_back(b), b = up[b][0]; while (a != b) { pa.push_back(a); a = up[a][0]; pb.push_back(b); b = up[b][0]; } pa.push_back(a); vector<int> path = pa; for (int i = (int)pb.size() - 1; i >= 0; i--) path.push_back(pb[i]); int m = (int)path.size(); const ll BIG = (ll)4e18; vector<ll> f(m, BIG); f[0] = v[path[0]]; for (int i = 1; i < m; i++) { for (int j = 1; j <= K && i - j >= 0; j++) { if (f[i - j] < BIG) { ll t2 = f[i - j] + v[path[i]]; if (t2 < f[i]) f[i] = t2; } // 若下一跳跨度已用满 K,则必须「断跳」:允许重新起跳, // 但重新起跳不产生额外代价(仍在同一条路径上连续)。 // 上面的转移已覆盖(允许从任意 i-j 起)。 } } printf("%lld\n", f[m - 1]); } return 0; }

四、代码分段拆解

代码不长,但每一行都有讲究,分段过一遍。

头文件与全局变量

先把用到的头文件和全局变量放上来。全局变量在竞赛里很常用,好处是函数之间共享方便,代价是要小心初始化和被意外改写。

#include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <string>
using namespace std; struct Mat { int n, Q, K; vector<int> adj[200005]; int dep[200005]; int up[200005][19]; int LOGN;

主体逻辑

主体部分按顺序完成读入、计算、输出。main函数尽量保持线性,把复杂的逻辑拆成独立函数,出问题时更容易定位。

状态与转移

DP 部分建议在代码里写上状态含义的注释。过几天回头看,没有注释的 DP 基本读不懂。

五、实例模拟

用题目给的样例走一遍,验证我们的思路。

样例 1

输入:

7 3 3 1 2 3 4 5 6 7 1 2 1 3 2 4 2 5 3 6 3 7 4 7 5 6 1 2

输出:

12 12 3

以第一组样例为例,输入是7 3 3、1 2 3 4 5 6 7、1 2、1 3、2 4、2 5、3 6、3 7、4 7、5 6、1 2,正确输出是12、12、3。建议把这两行数据抄在纸上,用上面的代码手动走一遍,重点看循环变量怎么变化、答案在哪一步被更新。能自己推出来的东西,比看懂代码印象深得多。

六、踩坑总结

把这题的经验固化下来。

  1. 状态定义要写清楚,最好注释在代码里
  2. 遍历顺序错了会用到本层刚更新的值,结果就偏了
  3. 多阶段 DP 每进入新阶段,之前的数组要重新初始化
  4. 高难度题的暴力分很关键,先写一版能过的部分分再想正解

另外提醒一句:这题在 CSP-S 2022 里是省选/NOI-难度,考场上如果没有把握拿满分,先把暴力写法交上去拿到部分分,再回头优化。CSP 是打分制,空着不写才是最亏的。

七、知识点拓展

这题的核心是动态规划。

CSP 的 DP 题很少单独考,通常和别的知识点结合。判断能不能用 DP,就看有没有「阶段 + 状态」的结构,以及当前决策会不会影响后面的选择。

建议的练习路径:

  1. 先把本题独立写一遍,不看代码,遇到卡壳再看提示
  2. 换个平台提交(ACGO 和洛谷都试试),确保代码在两个平台都能过
  3. 找两到三道同类型的题练手,巩固这类问题的识别能力

八、适用人群

这题适合这些阶段的同学:

  • 目标是 CSP-S 高分或省选的同学
  • 想通过真题理解高难度题如何从暴力优化到正解的同学
  • 想弄明白动态规划类问题标准解法的同学

有更好的写法欢迎在评论区交流,一起进步。

关于这个系列

这是「CSP 真题逐题精讲」的第 32 篇。我会按年份从 2019 一路刷到 2025,
CSP-J 和 CSP-S 全覆盖。每篇都给:双平台题号(ACGO + 洛谷)+ 逐行代码拆解 + 真实样例模拟。

跟着刷完这一套,CSP 第一轮的选择题和基础题基本就稳了。

下一篇:CSP-S 2022 星战(省选/NOI-)

👉 关注我,不错过后续更新。评论区告诉我你现在刷到哪一年了,我看看能不能按大家的进度调整更新节奏。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询