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\) 所选边边权之和。

于是易得转移:

\[dp_{x,v}=\max(dp_{x,v},dp_{i,k}+dp_{x,v-k-1}+w) \]

\(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;
}
posted @   _XOFqwq  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示