Living-Dream 系列笔记 第52期
本期题目均为 \(\texttt{II}\) 类树形 dp,即树上多重背包。
T1
令 \(dp_{i,j}\) 表示以 \(i\) 为根的子树保留 \(j\) 条边的最大边权。
答案即为 \(dp_{1,q}\),因为不管 \(1\) 连着的边选 / 不选,答案都会上传到根。
当然如果你愿意,求一遍 \(\max\{dp_{i,q}\}\) 也行。
易得初始状态:事实上这题没有初始状态。
我们现在考虑 \(cur\) 及其邻接点的转移。
目前 \(cur\) 拥有两个子节点 \(nxt1,nxt2\)。
显然 \(cur\) 所选的边应为 \(nxt1\) 所选的边 \(+\ nxt2\) 所选的边 $+\ 2$,
其中 \(+\ 2\) 是因为有 \(cur \to nxt1,nxt2\) 的边。
容易看出,\(nxt1\) 所选的边越多,\(nxt2\) 所选的边就越少,
这说明兄弟节点间存在数量约束关系,鉴定为 \(\texttt{II}\) 类树形 dp。
这启发我们进行树上多重背包。
具体地,我们统计出 \(cur\) 子树内边的数量 \(siz\) 与 \(q\) 的较小值作为背包的容量(因为 \(siz\) 有可能大于 \(q\))。
同时,我们将 \(nxt1,nxt2\) 分别看作两个物品,重量为 \(nxt1,nxt2\) 所选边数,价值为 \(nxt1,nxt2\) 所选边边权之和。
于是易得转移:
(\(x\) 为 \(cur\),\(v\) 为背包容量,\(i\) 为 \(nxt1/nxt2\),\(k\) 为枚举的 \(i\) 的所选边数)
即(下图以 \(nxt1\) 为例):
直接做即可。
code
#include<bits/stdc++.h> using namespace std; const int N=1e2+5; int n,q; int dp[N][N]; struct Edge{ int to,w; }; vector<Edge> G[N<<1]; int dfs(int x,int fa){ int siz=0; for(auto i:G[x]){ if(i.to==fa) continue; siz+=dfs(i.to,x)+1; for(int v=min(siz,q);v>=0;v--) for(int k=0;k<v;k++) dp[x][v]=max(dp[x][v],dp[x][v-k-1]+dp[i.to][k]+i.w); } return siz; } int main(){ cin>>n>>q; for(int i=1,u,v,w;i<n;i++) cin>>u>>v>>w, G[u].push_back({v,w}), G[v].push_back({u,w}); dfs(1,-1); cout<<dp[1][q]; return 0; }
T2
本题与上题的区别:
边权转为点权,树转为森林。
对于前者,
初始状态令 \(dp_{i,1}=s_i\) 即可,
\(siz\) 改为统计子树内点数即可,
转移改为 \(dp_{x,v}=\max(dp_{x,v},dp_{i,k}+dp_{x,v-k})\)(即无需单独考虑)即可。
对于后者,我们设超级源点 \(0\) 将森林合并为树即可。
其余与上题完全一致,此处不再赘述。
code
#include<bits/stdc++.h> using namespace std; const int N=3e2+5; int n,m; int dp[N][N]; vector<int> G[N<<1]; int dfs(int x){ int siz=1; for(int i:G[x]){ siz+=dfs(i); for(int v=min(siz,m);v>=0;v--) for(int k=0;k<v;k++) dp[x][v]=max(dp[x][v],dp[x][v-k]+dp[i][k]); } return siz; } int main(){ cin>>n>>m,m++; for(int i=1,u;i<=n;i++) cin>>u>>dp[i][1], G[u].push_back(i); dfs(0); cout<<dp[0][m]; return 0; }
T3
首先,我们将叶子节点的点权看作正,其余看作负,方便计算收益。
令 \(dp_{i,j}\) 表示以 \(i\) 为根的子树选取 \(j\) 个叶子节点的最大收益。
我们倒序枚举选取叶子节点的数量,若某一数量 \(i\) 满足 \(dp_{1,i} \ge 0\),则答案即为 \(i\)。
对于初始状态,因为有负点权,所以先均赋 \(-\infty\)。
对于一叶子节点 \(i\),显然有初始状态 $dp_{i,1}=\ $其点权。
对于其余中的一节点 \(i\),也显然有 \(dp_{i,0}=0\)。
转移与上题如出一辙,只是需多加一个负点权即可。
code
#include<bits/stdc++.h> using namespace std; const int N=3e3+5; int n,m,a[N]; int dp[N][N]; struct Edge{ int to,w; }; vector<Edge> G[N]; int dfs(int x){ int siz=0; if(x>n-m){ dp[x][1]=a[x]; return 1; } dp[x][0]=0; for(auto i:G[x]){ int son=dfs(i.to); siz+=son; for(int v=siz;v>=0;v--) for(int k=1;k<=son;k++) dp[x][v]=max(dp[x][v],dp[i.to][k]+dp[x][v-k]-i.w); } return siz; } int main(){ cin>>n>>m; memset(dp,0xcf,sizeof(dp)); for(int i=1,k,v,w;i<=n-m;i++){ cin>>k; for(int j=1;j<=k;j++) cin>>v>>w, G[i].push_back({v,w}); } for(int i=n-m+1;i<=n;i++) cin>>a[i]; dfs(1); for(int i=m;i>=1;i--) if(dp[1][i]>=0) cout<<i,exit(0); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现