[CSP-S 2022]数据传输 题解
upd:回看了一遍突然发现有好多地方写错了(比如前面是 后面突然变成 ),不过应该不影响阅读的。。吧,懒得改了。
提示:我这个方法比其它解法还要麻烦,目前最简洁的解法是 dottle 的解法,推荐阅读,我仅在此说明我的思路。
给定 个点的树,点 的点权为 ,给定整数 。
称一条路径是合法的,当且仅当路径上相邻两点在树上的距离不超过 。
定义一条路径的花费为路径上的点权之和。
次询问,每次给定 ,求 路径花费的最小值。
。
Preface
又是一年 CSP 啊,虽然比去年长进不少,可仔细看来变化还是不大,去年考场上看出了 T1 的做法却写不出来,今年面对 T4 亦是如此啊,从这道题学到了不少。
主要还是方法有点笨,以及写的过程中失误太多,考场下我花了将近 4h 才写完,在考场上根本没有这个时间。这说明考场上适当的决策也很重要。
此外,考场上我因为始终没有发现 时可以跳出链外,导致 样例一直过不去,而我一直没有发现这个问题,只是对着程序不断静态纠错,最后因为过于匆忙甚至 写挂,没删调试都没发现,最后原地爆炸。
还是要调整好心态,不管什么时候都要保持冷静。
Analysis
首先,看到 的范围,显然是要分类讨论。
k=1:
显然,最小花费即为 在树上路径的点权和。倍增预处理,每次查询 LCA 然后树上前缀和即可。
时间复杂度 。
k=2:
先考虑暴力的做法。将 在树上的路径拆下来,将点权写作序列 研究。
我们用动态规划解决这个问题。设 表示 的最小花费。
初始状态:。
状态转移方程:。
如果对矩阵很熟悉,不难发现这个就是一个矩阵优化的板子。
不过此处 是变量,无法进行矩阵快速幂,当时考场上想到这里我脑子莫名其妙宕机了,感觉这题正解绝对是矩阵,但这个东西不会处理啊,然后就彻底蒙了,完全想偏了,最后打了暴力。
实际上和快速幂没有任何关系。因为是树上的静态查询问题,自然可以往倍增想。
因为矩阵满足交换律和结合律,完全可以把矩阵先存起来预处理,最后查询的时候再一次倍增求解。
先把上面那个式子写成矩阵:
为了方便,我们定义 , 时亦然。
把中间的转移矩阵存起来倍增预处理一下,询问的时候,因为正着走和倒着走没有区别,我们把 拆成 两条链,以 为例:
初始向量矩阵:
然后把 上的转移矩阵全乘起来,就得到了 这条链上的答案。
类似地处理 ,接下来考虑合并答案。
因为 ,只有两种可能: 走到 再走到 ,或者不经过 ,直接从 在 路径上的两个子节点处穿过。
记 的矩阵为 , 的矩阵为 ,因为可以写作一维向量的形式,为了方便,我们直接将 中的元素用类似序列下标的形式表示,即 。下面 的情况里我们仍采用这种称呼。
针对两种情况,答案分别为 ,两者取 即可。
时空复杂度 。
k=3:
其实 大体思路和 完全一致,只不过有一个小细节:答案路径上的节点不一定都是 这条链上的。
原因很简单,比如链上的点权都是 级别,而与链相连的节点中有一个点权是 1,那走这个点显然更优。
题解里大部分做法是将距离设入状态中,我当时并没有想到,而是直接把链接出去的点再设计一个方程。
还是先考虑一条链上的暴力,因为与 相连的点中,只有点权最小的有效,设这个最小点权为 。
在 的情况上再加一条:设 表示走到 连接的点上的最小路径花费。
状态转移方程:
考虑将这个东西写成矩阵,那么样子就长这样:
倍增预处理和查询就很类似了,略过。
最后的合并情况很多,这里我直接把所有情况对应的式子列出来,因为用文字真的很难写出来 QAQ,理解一下叭 awa,有兴趣可以自己推一下,如果实在嫌我的方法麻烦还是看别的大佬的题解吧 qwq:
所有情况取 即可。时间复杂度 。
Code
// Problem: P8820 [CSP-S 2022] 数据传输 // Contest: Luogu // URL: https://www.luogu.com.cn/problem/P8820 // Memory Limit: 1 MB // Time Limit: 3000 ms // // Powered by CP Editor (https://cpeditor.org) #include <bits/stdc++.h> #define pb emplace_back using i64 = long long; const i64 INF = 1e16; const int maxn = 2e5 + 5; std::vector<int> G[maxn]; int n,m; int f[maxn][20],lg[maxn],dep[maxn]; i64 d[maxn],val[maxn],b[maxn]; void dfs(int u,int fa) { d[u] = d[fa] + val[u]; for(auto& v : G[u]) { if(v == fa)continue ; dep[v] = dep[u] + 1; f[v][0] = u; for(int k = 1;(1 << k) <= dep[v];++ k) f[v][k] = f[f[v][k - 1]][k - 1]; dfs(v , u); } return ; } int LCA(int u,int v) { if(dep[u] < dep[v])std::swap(u , v); for(int k = lg[dep[u]];~ k;-- k) { if(dep[u] - (1 << k) >= dep[v]) { u = f[u][k]; } if(u == v)return u; } for(int k = lg[dep[u]];~ k;-- k) { if(f[u][k] != f[v][k]) { u = f[u][k]; v = f[v][k]; } } return f[u][0]; } namespace subtask_1 { int main() { while(m --) { int u,v; scanf("%d %d",&u,&v); int t = LCA(u , v); printf("%lld\n",d[u] + d[v] - d[t] - d[f[t][0]]); } return 0; } } namespace subtask_2 { struct matrix { i64 g[2][2]; matrix() { g[0][0] = g[0][1] = g[1][0] = g[1][1] = INF; } void init() { g[0][0] = g[0][1] = g[1][0] = g[1][1] = INF; return ; } matrix operator * (const matrix& p)const { matrix a; for(int q = 0;q < 2;++ q) { for(int i = 0;i < 2;++ i) { for(int j = 0;j < 2;++ j) { a.g[i][j] = std::min(a.g[i][j] , g[i][q] + p.g[q][j]); } } } return a; } }S[maxn][18]; void DFS(int u,int fa) { for(auto& v : G[u]) { if(v == fa)continue ; S[v][0].g[0][0] = val[v]; S[v][0].g[0][1] = 0; S[v][0].g[1][0] = val[v]; S[v][0].g[1][1] = INF; for(int k = 1;(1 << k) <= dep[v];++ k) S[v][k] = S[v][k - 1] * S[f[v][k - 1]][k - 1]; DFS(v , u); } return ; } int main() { S[1][0].g[0][0] = val[1]; S[1][0].g[0][1] = 0; S[1][0].g[1][0] = val[1]; S[1][0].g[1][1] = INF; DFS(1 , 0); while(m --) { int u,v,t; scanf("%d %d",&u,&v); if(u == v) { printf("%lld\n",val[u]); continue ; } t = LCA(u , v); i64 ans = INF; matrix F,P; F.g[0][0] = val[u]; P.g[0][0] = val[v]; int x = f[u][0]; if(u != t) { for(int k = lg[dep[x]];~ k;-- k) { if(dep[x] < dep[t])break ; if(dep[f[x][k]] >= dep[t]) { F = F * S[x][k]; x = f[x][k]; } } while(dep[x] >= dep[t])F = F * S[x][0],x = f[x][0]; } if(v != t) { x = f[v][0]; for(int k = lg[dep[x]];~ k;-- k) { if(dep[x] < dep[t])break ; if(dep[f[x][k]] >= dep[t]) { P = P * S[x][k]; x = f[x][k]; } } while(dep[x] >= dep[t])P = P * S[x][0],x = f[x][0]; } ans = std::min(F.g[0][0] + P.g[0][0] - val[t] , F.g[0][1] + P.g[0][1]); printf("%lld\n",ans); } return 0; } } namespace subtask_3 { struct matrix { i64 g[5][5]; matrix() { for(int i = 0;i < 5;++ i) for(int j = 0;j < 5;++ j) g[i][j] = INF; } void init() { for(int i = 0;i < 5;++ i) for(int j = 0;j < 5;++ j) g[i][j] = INF; } matrix operator * (const matrix& p)const { matrix c; for(int k = 0;k < 5;++ k) for(int i = 0;i < 5;++ i) for(int j = 0;j < 5;++ j) c.g[i][j] = std::min(c.g[i][j] , g[i][k] + p.g[k][j]); return c; } }S[maxn][18]; void getinit(int u) { S[u][0].g[0][0] = val[u]; S[u][0].g[0][1] = 0; S[u][0].g[0][3] = b[u]; S[u][0].g[1][0] = val[u]; S[u][0].g[1][2] = 0; S[u][0].g[1][3] = b[u]; S[u][0].g[2][0] = val[u]; S[u][0].g[3][0] = val[u]; S[u][0].g[3][3] = b[u]; S[u][0].g[3][4] = 0; S[u][0].g[4][0] = val[u]; return ; } void DFS(int u,int fa) { for(auto& v : G[u]) { if(v == fa)continue ; getinit(v); for(int k = 1;(1 << k) <= dep[v];++ k) S[v][k] = S[v][k - 1] * S[f[v][k - 1]][k - 1]; DFS(v , u); } return ; } int main() { for(int i = 1;i <= n;++ i) { b[i] = INF; for(auto& v : G[i]) { b[i] = std::min(b[i] , val[v]); } } getinit(1); DFS(1 , 0); while(m --) { int u,v,t; scanf("%d %d",&u,&v); if(u == v) { printf("%lld\n",val[u]); continue ; } t = LCA(u , v); i64 ans = INF; matrix F,P; F.g[0][0] = val[u]; P.g[0][0] = val[v]; int x = f[u][0]; if(u != t) { for(int k = lg[dep[x]];~ k;-- k) { if(dep[x] < dep[t])break ; if(dep[f[x][k]] >= dep[t]) { F = F * S[x][k]; x = f[x][k]; } } while(dep[x] >= dep[t])F = F * S[x][0],x = f[x][0]; } if(v != t) { x = f[v][0]; for(int k = lg[dep[x]];~ k;-- k) { if(dep[x] < dep[t])break ; if(dep[f[x][k]] >= dep[t]) { P = P * S[x][k]; x = f[x][k]; } } while(dep[x] >= dep[t])P = P * S[x][0],x = f[x][0]; } ans = std::min(ans , F.g[0][0] + P.g[0][0] - val[t]); ans = std::min(ans , F.g[0][3] + P.g[0][3] - b[t]); ans = std::min(ans , F.g[0][1] + P.g[0][1]); ans = std::min(ans , F.g[0][1] + P.g[0][2]); ans = std::min(ans , F.g[0][1] + P.g[0][4]); ans = std::min(ans , F.g[0][4] + P.g[0][1]); ans = std::min(ans , F.g[0][2] + P.g[0][1]); printf("%lld\n",ans); } return 0; } } int k; int main() { scanf("%d %d %d",&n,&m,&k); for(int i = 1;i <= n;++ i) scanf("%lld",&val[i]); for(int i = 1;i < n;++ i) { int u,v; scanf("%d %d",&u,&v); G[u].pb(v); G[v].pb(u); } dep[0] = -1; for(int i = 2;i <= n;++ i) lg[i] = lg[i >> 1] + 1; dfs(1 , 0); if(k == 1) { subtask_1::main(); return 0; } if(k == 2) { subtask_2::main(); return 0; } else subtask_3::main(); return 0; }
完结撒花✿✿ヽ(°▽°)ノ✿
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)