纪念一下再次调出CSP-S 2022 T4了
哈!鸽了一年的题解!
本来应该去年就补上的,但是这只鸽子一直在咕咕咕。
[CSP-S 2022] 数据传输 题解
我们分 $k = 1, 2, 3$ 的情况讨论。
$k = 1$
显然 $s$ 到 $t$ 的简单路径上所有点都会产生贡献,所以简单树上差分即可。
$k = 2$
这个时候仍然是在 $s$ 到 $t$ 的简单路径上处理,因为假设从 $s$ 到 $t$ 经过了外部结点,那肯定不如只走简单路径更优(因为点权均为正数)。
这个时候假设把这条路径提出来了,那就是一个线性dp了(类似于打家劫舍?)。
这个线性dp要加速的话可以用矩阵,具体来说令 $f_{i}$ 表示从起点走到路径上的第 $i$ 个结点所需要的最小费用,那么转移方程式就是:
$$ f_{i} = \min(f_{i - 1}, f_{i - 2}) + v_{i} $$
我们定义新矩阵乘法 $\times$:大小为 $N \times M$ 的 $A$ 矩阵和大小为 $M \times R$ 的 $B$ 矩阵相乘得到大小为 $N \times R$ 的 $C$ 矩阵,$C$ 矩阵第 $i$ 行第 $j$ 列的元素满足:
$$ C_{i, j} = \min\limits_{1 \leqslant k \leqslant M}\left\{A_{i, k} + B_{k, j}\right\} $$
可以发现,新矩阵乘法和一般矩阵乘法一样满足结合律,但不满足交换律。
类似的,对于这种矩阵乘法存在单位矩阵,即对角线为 $0$ 其余元素为 $\infty$ 的矩阵。
将dp的状态转移方程式改成矩阵 $\times$,可以得到:
$$ \begin{bmatrix} f_{i}\\ f_{i - 1} \end{bmatrix} \times \begin{bmatrix} v_{i}&v_{i}\\ 0&\infty \end{bmatrix} = \begin{bmatrix} f_{i + 1}\\ f_{i} \end{bmatrix} $$
并且因为这个矩阵 $\times$ 满足结合律并且题目给出的信息是静态的,可以直接把每个结点的转移矩阵处理出来,在倍增求 LCA 的同时计算就可以了。
$k = 3$
这个时候就比较复杂了。
因为需要dp的就不是 $s$ 到 $t$ 的简单路径了,准确的来说,它是一条毛毛虫,就是一条链上的每个结点都挂着很多其它结点(这道题起始节点没有)。
但是可以发现如果跳到某个结点的附属结点,那么一定是跳到最小值上。
于是令这个最小值为 $mn_{i}$。
来想dp的转移方程,令 $f_{i, 0/1}$ 表示走到路径第 $i$ 个结点/走到路径第 $i$ 个结点的附属结点的最小花费,于是有转移方程:
$$ \begin{cases} f_{i, 0} = \min\left\{f_{i - 3, 0}, f_{i - 2, 0}, f_{i - 2, 1}, f_{i - 1, 0}, f_{i - 1, 1}\right\} + v_{i}\\ f_{i, 1} = \min\left\{f_{i - 2, 0}, f_{i - 1, 0}, f_{i - 1, 1}\right\} + mn_{i} \end{cases} $$
但是这样需要保存的状态太多了,就算用矩阵加速也会因超大常数T掉。
尝试优化状态设计,发现我们的状态转移并不关心是跳到了哪个结点或哪个结点的附属结点,只用考虑所在结点与当前节点的距离。
于是令 $f_{i, 0/1/2}$ 表示走到距第 $i$ 个结点的距离为 $0/1/2$ 时的最小花费,有状态转移方程:
$$ \begin{cases} f_{i, 0} = \min\left\{f_{i - 1, 0}, f_{i - 1, 1}, f_{i - 1, 2}\right\} + v_{i}\\ f_{i, 1} = \min\left\{\min\left\{f_{i - 1, 0}, f_{i - 1, 1}\right\} + nm_{i},f_{i - 1, 0}\right\}\\ f_{i, 2} = f_{i - 1, 1} \end{cases} $$
改写成矩阵:
$$ \newcommand{\type}{bmatrix} \newcommand{\m}[1]{\begin{\type}#1\end{\type}} \m{ f_{i - 1, 0}\\ f_{i - 1, 1}\\ f_{i - 1, 2}\\ } \times \m{ v_{i}&v_{i}&v_{i}\\ 0&s_{i}&\infty\\ \infty&0&\infty\\ } = \m{ f_{i, 0}\\ f_{i, 1}\\ f_{i, 2}\\ } $$
然后就可以用 $k = 2$ 时类似的倍增来做了!
注意在处理 LCA 的时候附属结点会包含 LCA 的父结点,计算 $mn_{LCA}$ 的时候要把 $v_{father_{LCA}}$ 也算上。
整体代码比较复杂,我调试的时候改来改去把矩阵 $\times$ 改得很奇怪才过,也不知道为什么。
这里提供两个版本的代码,一个是针对 $k = 1, 2, 3$ 三种情况分别有一个 Solution 的代码,一个是把 $k = 1, 2, 3$ 三种情况融合在一起的,两个代码都把模板省略了。
给一个矩阵转移的方程(分别是 $k = 1, 2, 3$ 时的,多余的状态只是为了配合 $3 \times 3$ 的矩阵乘法,没有太大意义):
$$ \newcommand{\type}{bmatrix} \newcommand{\m}[1]{\begin{\type}#1\end{\type}} \m{ f_{i - 1, 0}\\ f_{i - 1, 1}\\ f_{i - 1, 2}\\ } \times \m{ v_{i}&\infty&\infty\\ \infty&0&\infty\\ \infty&\infty&0\\ } = \m{ f_{i, 0}\\ f_{i, 1}\\ f_{i, 2}\\ } \\\\ \m{ f_{i - 1, 0}\\ f_{i - 1, 1}\\ f_{i - 1, 2}\\ } \times \m{ v_{i}&v_{i}&\infty\\ 0&\infty&\infty\\ \infty&\infty&0\\ } = \m{ f_{i, 0}\\ f_{i, 1}\\ f_{i, 2}\\ } \\\\ \m{ f_{i - 1, 0}\\ f_{i - 1, 1}\\ f_{i - 1, 2}\\ } \times \m{ v_{i}&v_{i}&v_{i}\\ 0&s_{i}&\infty\\ \infty&0&\infty\\ } = \m{ f_{i, 0}\\ f_{i, 1}\\ f_{i, 2}\\ } $$
分类的代码:
const ll inf = 1ll << 60;
ll n, q, k, a, b, lg[200001], v[200001], dep[200001], fa[200001][19];
vector<int> g[200001];
void dfs(int now, int father) {
dep[now] = dep[father] + 1;
fa[now][0] = father;
fu(i, 1, lg[n]) fa[now][i] = fa[fa[now][i - 1]][i - 1];
for(const auto& i : g[now]) {
if(i != father) dfs(i, now);
}
}
int lca(int x, int y) {
if(dep[x] < dep[y]) swap(x, y);
while(dep[x] > dep[y]) x = fa[x][lg[dep[x] - dep[y]]];
if(x == y) return x;
fd(i, lg[n], 0) {
if(fa[x][i] != fa[y][i]) {
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
namespace Solution1 {
ll sum[200001];
void dfs(int now, int father) {
sum[now] = sum[father] + v[now];
for(const auto& i : g[now]) {
if(i != father) dfs(i, now);
}
}
void init() {
dfs(1, 0);
}
ll solve(int s, int t) {
int LCA = lca(s, t);
return sum[s] + sum[t] - sum[LCA] - sum[fa[LCA][0]];
}
}
namespace Solution2 {
struct matrix {
ll v[2][2];
matrix(ll V = inf) {v[0][0] = v[1][1] = V; v[0][1] = v[1][0] = inf;}
matrix operator * (const matrix& b) const {
matrix ret;
fu(i, 0, 1) {
fu(j, 0, 1) {
fu(k, 0, 1) {
ret.v[i][k] = min(ret.v[i][k], v[i][j] + b.v[j][k]);
}
}
}
return ret;
}
};
matrix up[200001][19], down[200001][19];
matrix get_matrix(int x) {
matrix ret;
ret.v[0][0] = ret.v[1][0] = v[x];
ret.v[0][1] = 0;
return ret;
}
void init() {
fu(i, 1, n) {
up[i][0] = down[i][0] = get_matrix(i);
}
fu(i, 1, lg[n]) {
fu(j, 1, n) {
up[j][i] = up[j][i - 1] * up[fa[j][i - 1]][i - 1];
down[j][i] = down[fa[j][i - 1]][i - 1] * down[j][i - 1];
}
}
}
matrix get_up(int x, int y) {
matrix ret(0);
while(dep[x] > dep[y]) ret = ret * up[x][lg[dep[x] - dep[y]]], x = fa[x][lg[dep[x] - dep[y]]];
return ret;
}
matrix get_down(int x, int y) {
matrix ret(0);
while(dep[x] > dep[y]) ret = down[x][lg[dep[x] - dep[y]]] * ret, x = fa[x][lg[dep[x] - dep[y]]];
return ret;
}
ll solve(int s, int t) {
int LCA = lca(s, t);
matrix ans = get_up(s, LCA) * get_matrix(LCA) * get_down(t, LCA);
return min(ans.v[1][0], ans.v[1][1] + v[t]);
}
}
namespace Solution3 {
struct matrix {
ll v[3][3];
matrix(ll V = inf) {v[0][0] = v[1][1] = v[2][2] = V; v[0][1] = v[0][2] = v[1][0] = v[1][2] = v[2][0] = v[2][1] = inf;}
matrix operator * (const matrix& b) const {
matrix ret;
fu(i, 0, 2) {
fu(j, 0, 2) {
fu(k, 0, 2) {
ret.v[i][k] = min(ret.v[i][k], v[i][j] + b.v[j][k]);
}
}
}
return ret;
}
};
matrix up[200001][19], down[200001][19];
ll min_son[200001];
void dfs(int now, int father) {
min_son[now] = inf;
for(const auto& i : g[now]) {
if(i != father) {
dfs(i, now);
min_son[now] = min(min_son[now], v[i]);
}
}
}
matrix get_matrix(int x, ll V = inf) {
matrix ret;
ret.v[0][0] = ret.v[1][0] = ret.v[2][0] = v[x];
ret.v[0][1] = 0, ret.v[1][1] = min(min_son[x], V);
ret.v[1][2] = 0;
return ret;
}
void init() {
dfs(1, 0);
fu(i, 1, n) {
up[i][0] = down[i][0] = get_matrix(i);
}
fu(i, 1, lg[n]) {
fu(j, 1, n) {
up[j][i] = up[j][i - 1] * up[fa[j][i - 1]][i - 1];
down[j][i] = down[fa[j][i - 1]][i - 1] * down[j][i - 1];
}
}
}
matrix get_up(int x, int y) {
matrix ret(0);
while(dep[x] > dep[y]) ret = ret * up[x][lg[dep[x] - dep[y]]], x = fa[x][lg[dep[x] - dep[y]]];
return ret;
}
matrix get_down(int x, int y) {
matrix ret(0);
while(dep[x] > dep[y]) ret = down[x][lg[dep[x] - dep[y]]] * ret, x = fa[x][lg[dep[x] - dep[y]]];
return ret;
}
ll solve(int s, int t) {
int LCA = lca(s, t);
matrix ans = get_up(s, LCA) * get_matrix(LCA, v[fa[LCA][0]]) * get_down(t, LCA);
return min(ans.v[2][0], min(ans.v[2][1], ans.v[2][2]) + v[t]);
}
}
int main() {
read(n, q, k);
v[0] = inf;
fu(i, 1, n) read(v[i]);
fu(i, 2, n) {
read(a, b);
g[a].push_back(b);
g[b].push_back(a);
lg[i] = lg[i >> 1] + 1;
}
dfs(1, 0);
switch(k) {
case 1: Solution1::init(); break;
case 2: Solution2::init(); break;
case 3: Solution3::init(); break;
}
while(q--) {
read(a, b);
switch(k) {
case 1: write(Solution1::solve(a, b), '\n'); break;
case 2: write(Solution2::solve(a, b), '\n'); break;
case 3: write(Solution3::solve(a, b), '\n'); break;
}
}
flush();
return 0;
}
融合的代码:
const ll inf = 1ll << 60;
struct matrix {
ll v[3][3];
matrix(ll val = inf) {
v[0][0] = val, v[0][1] = inf, v[0][2] = inf;
v[1][0] = inf, v[1][1] = val, v[1][2] = inf;
v[2][0] = inf, v[2][1] = inf, v[2][2] = val;
}
matrix operator = (const matrix& b) {
v[0][0] = b.v[0][0], v[0][1] = b.v[0][1], v[0][2] = b.v[0][2];
v[1][0] = b.v[1][0], v[1][1] = b.v[1][1], v[1][2] = b.v[1][2];
v[2][0] = b.v[2][0], v[2][1] = b.v[2][1], v[2][2] = b.v[2][2];
return *this;
}
matrix operator * (const matrix& b) const {
matrix ret;
fu(i, 0, 2) {
fu(j, 0, 2) {
fu(k, 0, 2) {
ret.v[i][k] = min(ret.v[i][k], v[i][j] + b.v[j][k]);
}
}
}
return ret;
}
} dp, up[200001][19], dn[200001][19];
int n, q, k, a, b, LCA, lg[200001], dep[200001], fa[200001][19];
ll v[200001], mn[200001];
vector<int> g[200001];
matrix get(int i, ll f = inf) {
matrix ret;
switch(k) {
case 1: {
ret.v[0][0] = v[i];
break;
}
case 2: {
ret.v[0][0] = v[i], ret.v[1][0] = v[i];
ret.v[0][1] = 0, ret.v[1][1] = inf;
break;
}
case 3: {
ret.v[0][0] = v[i], ret.v[1][0] = v[i], ret.v[2][0] = v[i];
ret.v[0][1] = 0, ret.v[1][1] = min(mn[i], f), ret.v[2][1] = inf;
ret.v[0][2] = inf, ret.v[1][2] = 0, ret.v[2][2] = inf;
break;
}
}
return ret;
}
void dfs(int now, int father) {
fa[now][0] = father, dep[now] = dep[father] + 1;
fu(i, 1, lg[n]) {
fa[now][i] = fa[fa[now][i - 1]][i - 1];
}
mn[now] = inf;
fo(i, g[now]) {
if(i != father) {
dfs(i, now);
mn[now] = min(mn[now], v[i]);
}
}
up[now][0] = dn[now][0] = get(now);
}
int lca(int x, int y) {
if(dep[x] < dep[y]) swap(x, y);
while(dep[x] > dep[y]) x = fa[x][lg[dep[x] - dep[y]]];
if(x == y) return x;
fd(i, lg[n], 0) {
if(fa[x][i] != fa[y][i]) {
x = fa[x][i], y = fa[y][i];
}
}
return fa[x][0];
}
matrix upm(int x, int LCA) {
matrix ret(0);
while(dep[x] > dep[LCA]) {
ret = ret * up[x][lg[dep[x] - dep[LCA]]];
x = fa[x][lg[dep[x] - dep[LCA]]];
}
return ret;
}
matrix dnm(int x, int LCA) {
matrix ret(0);
while(dep[x] > dep[LCA]) {
ret = dn[x][lg[dep[x] - dep[LCA]]] * ret;
x = fa[x][lg[dep[x] - dep[LCA]]];
}
return ret;
}
signed main() {
read(n, q, k);
fu(i, 1, n) read(v[i]);
v[0] = inf;
fu(i, 2, n) {
lg[i] = lg[i >> 1] + 1;
read(a, b);
g[a].push_back(b);
g[b].push_back(a);
}
dfs(1, 0);
fu(i, 1, lg[n]) {
fu(j, 1, n) {
up[j][i] = up[j][i - 1] * up[fa[j][i - 1]][i - 1];
dn[j][i] = dn[fa[j][i - 1]][i - 1] * dn[j][i - 1];
}
}
while(q--) {
read(a, b);
LCA = lca(a, b);
dp = upm(a, LCA) * get(LCA, v[fa[LCA][0]]) * dnm(b, LCA);
switch(k) {
case 1: {
write(dp.v[0][0], '\n');
break;
}
case 2: {
write(min(dp.v[1][0], dp.v[1][1] + v[b]), '\n');
break;
}
case 3: {
write(min(dp.v[2][0], min(dp.v[2][1], dp.v[2][2]) + v[b]), '\n');
break;
}
}
}
flush();
return 0;
}
本文来自博客园,作者:A_box_of_yogurt,转载请注明原文链接:https://www.cnblogs.com/A-box-of-yogurt/p/18016417
【推荐】国内首个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)