CSP2025 - 树形 DP

news/2025/1/17 14:35:39/文章来源:https://www.cnblogs.com/laoshan-plus/p/18593615

CSP2025 - 树形 DP

T1 「MXOI Round 1」城市

这个 “树上两点距离之和” 很典,让我们想到换根 DP。

首先求出 \(\text{siz}_u\)\(d_u\),分别表示子树 \(u\) 的大小和子树所有点到 \(u\) 的距离之和。

接下来求出整棵树的所有点到 \(\boldsymbol u\) 的距离之和。考虑用 \(d_u\) 推出 \(d_v\),我们发现从 \(d_u\)\(d_v\) 少了 \(\text{siz}_v\)\(w(u,v)\) 的贡献,多了 \(n-\text{siz}_v\)\(w(u,v)\) 的贡献,所以第二遍 DFS 的转移方程就是

\[d_v=d_u+(n-2\times\text{siz}_v)\times w(u,v)~. \]

然后考虑第 \(n+1\) 个点加入的情况。对于新建的 \((k,n+1)\) 这条边,会在 \(\sum d_i\) 的基础上多出 \(2\times\big(d_k+n\times w(k,n+1)\big)\) 的贡献(乘 \(2\) 是因为双向边),于是查询就 \(O(1)\) 了。

总体复杂度 \(O(n)\)

int siz[MAXN],d[MAXN],ans;
void dfs(int u,int fno){siz[u]=1;for(int i=head[u];i;i=e[i].to){if(e[i].v==fno) continue;dfs(e[i].v,u);siz[u]+=siz[e[i].v];add(d[u],(d[e[i].v]+1ll*siz[e[i].v]*e[i].w%MOD)%MOD);}
}
void dfs2(int u,int fno){for(int i=head[u];i;i=e[i].to)if(e[i].v^fno){d[e[i].v]=(d[u]+(n-2ll*siz[e[i].v]%MOD+MOD)%MOD*e[i].w%MOD)%MOD;dfs2(e[i].v,u);}
}int main(){n=read(),q=read();for(int i=1,u,v,w;i<n;++i){u=read(),v=read(),w=read();addedge(u,v,w);addedge(v,u,w);}dfs(1,0);dfs2(1,0);for(int i=1;i<=n;++i) add(ans,d[i]);while(q--){int k=read(),w=read();write((ans+2ll*d[k]+2ll*n*w)%MOD);}return fw,0;
}

T2 [CF1285D] Dr. Evil Underscores

其实这题就是个贪心。首先异或 \(\to\) 字典树。把所有数扔到字典树上,然后按二进制位从高到低贪:如果当前节点有两个儿子,取两个儿子的最小值再加上当前位置的贡献;如果有一个儿子,就填成和这个儿子相同的位,不产生贡献。

就没了。

int n,tot=1;
int trie[MAXN][2];
void insert(int x){int p=1;for(int i=30;~i;--i){int c=(x>>i)&1;if(!trie[p][c]) trie[p][c]=++tot;p=trie[p][c];}
}
int ask(int bit,int p){if(bit<0) return 0;if(trie[p][0]&&trie[p][1]) return min(ask(bit-1,trie[p][0]),ask(bit-1,trie[p][1]))+(1<<bit);if(trie[p][0]) return ask(bit-1,trie[p][0]);if(trie[p][1]) return ask(bit-1,trie[p][1]);return 0;
}int main(){n=read();for(int i=1;i<=n;++i) insert(read());printf("%d\n",ask(30,1));return 0;
}

T3 P1270 “访问”美术馆

感觉这题的读入难度大于 DP 难度

首先建边的时候就把边权建成 \(2\) 倍,这样就计算了往返的时间。然后树上背包,设 \(f_{i,j}\) 表示节点 \(i\) 剩余时间为 \(j\) 的最大答案。则有转移方程

\[f_{u,i}=\max_{v\in\text{son}_u,w(u,v)\le j\le i}\{f_{u,i-j}+f_{v,j-w(u,v)}\}~. \]

在叶子节点时,初始化边界条件 \(f_{u,i}=\min(a_u,f_{u,i-5}+1)\)

注意如果转移的时候 \(i\)\(n\)\(w\) 枚举复杂度会假,所以我们需要处理 \(\text{siz}_u\) 表示偷完 \(u\) 点所有的东西再回到 \(u\) 点所需要的时间,然后优化转移上界就不会 T 了。\(j\) 也可以优化。

#include<bits/stdc++.h>
using namespace std;constexpr int MAXN=6005;
int n,tot=2,son[MAXN],siz[MAXN],f[MAXN][MAXN];
vector<pair<int,int>>g[MAXN];
void init(int u,int fno){int w,v;scanf("%d%d",&w,&v);g[fno].emplace_back(u,w<<1);if(v) return son[u]=v,void();init(++tot,u);init(++tot,u);
}
void dfs(int u){if(son[u]){siz[u]=son[u]*5;for(int i=5;i<=n;++i) f[u][i]=min(son[u],f[u][i-5]+1);return;}for(auto vv:g[u]){int v=vv.first,w=vv.second;dfs(v);siz[u]+=siz[v]+w;for(int i=min(n,siz[u]);i>=w;--i)for(int j=w;j<=min(i,siz[v]+w);++j)f[u][i]=max(f[u][i],f[u][i-j]+f[v][j-w]);}
}int main(){scanf("%d",&n);init(2,1);dfs(1);printf("%d\n",f[1][n-1]);return 0;
}

T4 [HAOI2015] 树上染色

将 “黑点两两距离” 和 “白点两两距离” 转化为针对计算贡献,也就是对于每一条边所经过的次数,乘上边权再加起来就是总的答案。

而每一条边被经过的次数等于边的两侧同色点的个数的乘积。于是一条边被经过的次数就是:

\[\mathit{tot}=k(m-k)+(\text{siz}_v-k)(n-m-\text{siz}_v+k) \]

其中 \(m\) 是题目要求选的黑点数,\(\text{siz}_v\) 表示 \(v\) 的子树大小,\(k\) 表示当前子树上已选择的黑点数。

DP 方程又是很熟悉的树上背包。设 \(f_{i,j}\) 表示节点 \(i\)\(j\) 个黑点的答案,有

\[f_{u,j}=\max_{v\in\text{son}_u}\{f_{u,j-k}+f_{v,k}+\mathit{tot}\times w(u,v)\} \]

树上背包需要通过 \(\textbf{siz}\) 优化转移边界。把 \(k=0\) 的情况提到循环外面作为边界的预处理。

ll siz[MAXN],f[MAXN][MAXN];
void dfs(int u,int fno){siz[u]=1;f[u][0]=f[u][1]=0;for(int i=head[u],v,w;i;i=e[i].to){if((w=e[i].w,v=e[i].v)==fno) continue;dfs(v,u);siz[u]+=siz[v];for(int j=min(1ll*m,siz[u]);~j;--j){if(~f[u][j]) f[u][j]+=f[v][0]+siz[v]*(n-m-siz[v])*w;for(int k=min(1ll*j,siz[v]);k;--k)if(~f[u][j-k]){ll tot=1ll*k*(m-k)+(siz[v]-k)*(n-m-siz[v]+k);f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]+tot*w);}}}
}int main(){scanf("%d%d",&n,&m);if(n-m<m) m=n-m;for(int i=1,u,v,w;i<n;++i){scanf("%d%d%d",&u,&v,&w);addedge(u,v,w),addedge(v,u,w);}memset(f,-1,sizeof(f));dfs(1,0);printf("%lld\n",f[1][m]);return 0;
}

T9 P5007 DDOSvoid 的疑惑

手画一下发现一定是同父亲的两个子树之间会产生贡献,这个贡献的产生方式为:在这些子树中任选一些,把这些子树的权值加起来就是贡献。

于是,我们需要记录两个值:\(f_u\)\(g_u\),分别表示节点 \(u\) 的贡献和 “毒瘤集” 的数量。先考虑 \(g_u\) 的转移。把上面所说的 “任选一些” 转化为:每新增一个点 \(v\),都可以和已有的所有点相匹配,所以有

\[g_u\gets g_u+g_u\times g_v~. \]

然后考虑 \(f_u\) 的转移,依然是每新增一个点都可以和之前的所有点匹配,有

\[f_u\gets f_u+f_u\times g_v+f_v\times g_u+f_v~. \]

至于这里为什么要分别算两遍 \(f_u\times g_v\)\(f_v\times g_u\),个人认为是因为多个元素构成的集合所产生的贡献也是多个元素的,所以匹配两遍才不会算漏。但为什么不会算重,题解区好像没有合理的解释,只能说 DP 还是太深奥了。

最后还需要给 \(f,g\) 加上当前点的贡献。

ll f[MAXN],g[MAXN];
void dfs(int u,int fno){for(int i=head[u],v;i;i=e[i].to){if((v=e[i].v)==fno) continue;dfs(v,u);add(f[u],f[u]*g[v]%MOD+f[v]*g[u]%MOD+f[v]);add(g[u],g[u]*g[v]%MOD+g[v]);}add(f[u],T?u:1);++g[u];
}int main(){n=read(),T=read();for(int i=1,u,v;i<n;++i){u=read(),v=read();addedge(u,v),addedge(v,u);}dfs(1,0);printf("%lld\n",f[1]);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/870775.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

瑞芯微开发板/主板Android配置APK默认开启性能模式方法

本文介绍瑞芯微开发板/主板Android配置APK默认开启性能模式方法,开启性能模式后,APK的CPU使用优先级会有所提高。触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行…

深入解析 Spring AI 系列:分析 Spring AI 可观测性

今天我们将讨论之前略过的可观测性部分的代码。在这里,我想简单说明一下,当时这部分代码属于必须编写的固定模板,因此在最初的讨论中我们直接跳过了它。虽然这部分代码乍看之下可能显得比较复杂,但实际上它的核心功能只是链路追踪的实现而已。既然如此,接下来我们就不再赘…

【Maldev】Early Bird 注入

一、介绍 QueueUserAPC 用于执行本地 APC 注入,APC 注入利用需要一个已挂起或可警报的线程才能成功执行 Payload。但是很难碰到处于这些状态的线程,尤其是以普通用户权限运行的线程,而Early Bird注入则是利用CreateProcess WinAPI 创建一个挂起的进程,并使用其挂起线程的句…

ThreadPool解析

Thread_Pool 项目解析 简介 ThreadPool 是一个轻量级的 C++ 线程池实现,旨在简化多线程编程。 项目分析 我们首先上github的项目地址:https://github.com/progschj/ThreadPool,然后克隆项目到本地。点开项目的ThrealPool.h文件,查看源码: #ifndef THREAD_POOL_H #define TH…

[docker逃逸] 使用DirtyPipe漏洞逃逸

本文作者CVE-柠檬i CSDN:https://blog.csdn.net/weixin_49125123 博客园:https://www.cnblogs.com/CVE-Lemon 微信公众号:Lemon安全 前言 本文使用代码下载链接:利用CVE-2022-0847 (Dirty Pipe) 实现容器逃逸 (github.com) 由于本人才疏学浅,本文不涉及漏洞原理,仅有复现…

RestAPI实现聚合

API语法 聚合条件与query条件同级别,因此需要使用request.source()来指定聚合条件。聚合的结果解析:@Override public Map<String, List<String>> filters(RequestParams params) {try {// 1.准备RequestSearchRequest request = new SearchRequest("hotel&…

elasticsearch之数据聚合

**聚合(aggregations)**可以让我们极其方便的实现对数据的统计、分析、运算。例如:什么品牌的手机最受欢迎? 这些手机的平均价格、最高价格、最低价格? 这些手机每月的销售情况如何?实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现近实时搜索效…

【通讯协议】OPC协议

OPC通讯协议 特点:支持多种数据结构和负责数据类型,需要多的硬件和软件资源,成本较高,安全性较高。 应用场景:连接多个不同工业自动化设备 什么是OPC通讯协议 OPC是英文“OLE for Process Control”的缩写,是工业自动化领域中的一种工业通信标准。它通过定义一些在不同平…

海外泼天流量|浅谈全球化技术架构

本文对海外泼天流量现状做了快速整理,旨在抛砖引玉,促进国内企业在出海过程中,交流如何构建全球化技术架构的落地经验,相信会有越来越多资深人士分享更深层次的实践。 登陆小红书,搜索 refugee,你就能看到一个不一样的小红书。随机点击几个,让大数据记住你,就能持续看到…

绿联网卡

目录1: 安装2:检查3:常见问题网络连接有网卡,状态为已禁用 1: 安装插入电脑 弹窗“Setup.exe”,安装驱动, 如果没有驱动,则找到 Ugreen Wireless进行驱动安装。驱动安装成功后效果2:检查驱动安装好后,u盘插拔一下,观察确定是哪个WLAN3:常见问题 网络连接有网卡,状态为…

kali安装教程

kali和GNOME桌面安装教程 kali下载 https://www.kali.org/get-kali/ 到kali官网,下载镜像安装下载完应该是:kali-linux-2024.4-installer-amd64.iso 然后新建虚拟机选择稍后安装操作系统:选择如图所示操作系统 后面的,我都给的挺多,主要不想它卡,哈哈哈网络选择NAT就行,…