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;
}

浙公网安备 33010602011771号