专栏文章
二叉苹果树
个人记录参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @miostgrx
- 此快照首次捕获于
- 2025/12/03 00:34 3 个月前
- 此快照最后确认于
- 2025/12/03 00:34 3 个月前
二叉苹果树
题目链接
凌晨0:31
分析
给你一棵树,只能保留 个树枝(边),最大的苹果数量
因为是在树上,又有容量(树枝个数)和最大价值,不难想到树上背包
树上背包
所以设 为 以 为根的子树中,前 个子树,选 个树枝,最大的苹果数
由背包问题的优化,可以换为滚动数组,将 一维压缩,再倒序遍历
那么从节点 一层一层地往下枚举,枚举每个子树根 ,可得:
其中 是除 子树外树枝数
那么实现便是
DFS代码
CPPvoid dfs(int x,int fa){//x:以x为根的子树 fa:当前节点的父亲(不重复)
for(auto i:g[x]){//遍历子树
if(i.first==fa){//遍历到父亲,就下一个(跳过)
continue;
}
dfs(i.first,x);//统计子树信息
for(int j=q;j>=1;j--){//枚举容量(树枝数) 原为 dp[i][j][k] 以i为子树,前j个枝,选k个,苹果数最大,压维后需逆序
for(int k=0;k<j;k++){//枚举当前树枝容量
dp[x][j]=max(dp[x][j],dp[x][j-k-1]+dp[i.first][k]+i.second);//更新,注意是j-k-1不是j-k,因为 x->i 连边也应加上
}
}
}
return;
}
时间复杂度 ,对于这道题的 来说已经足够了
优化
但还可以再优化一点
因为 是当前子树的树枝个数,但 ,所以只用枚举到当前子树 即可,而同理, 也只需枚举
跑一遍预处理 数组
预处理
CPPvoid solve(int x,int fa){
for(auto i:g[x]){//遍历子树
if(i.first==fa){//遍历到父亲,就下一个(跳过)
continue;
}
solve(i.first,x);//统计子树信息
siz[x]+=siz[i.first]+1;//树枝数=字数树枝数+1
}
return;
}
再判断
注意这里 要加1,因为是 ,但是是可以选 个的
AC Code
请勿copy
CPP//二叉苹果树
//树形dp
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
int n,q;
vector<pair<int,int>>g[310];//链式前向星亦可
int dp[110][110];//dp[i][j]表示在以i为根的子树中,留下j个树枝,最多能留住的苹果数量
int siz[110];
void solve(int x,int fa){
for(auto i:g[x]){//遍历子树
if(i.first==fa){//遍历到父亲,就下一个(跳过)
continue;
}
solve(i.first,x);//统计子树信息
siz[x]+=siz[i.first]+1;//树枝数=字数树枝数+1
}
return;
}
void dfs(int x,int fa){//x:以x为根的子树 fa:当前节点的父亲(不重复)
for(auto i:g[x]){//遍历子树
if(i.first==fa){//遍历到父亲,就下一个(跳过)
continue;
}
dfs(i.first,x);//统计子树信息
for(int j=min(siz[x],q);j>=1;j--){//枚举容量(树枝数) 原为 dp[i][j][k] 以i为子树,前j个枝,选k个,苹果数最大,压维后需逆序
for(int k=0;k<min(siz[i.first]+1,j);k++){//枚举当前树枝容量 (<!)
dp[x][j]=max(dp[x][j],dp[x][j-k-1]+dp[i.first][k]+i.second);//更新,注意是j-k-1不是j-k,因为 x->i 连边也应加上
}
}
}
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>q;
for(int i=1;i<n;i++){
int a,b,c;
cin>>a>>b>>c;
g[a].push_back({b,c});
g[b].push_back({a,c});//存树(图)
}
solve(1,0);
dfs(1,0);//从1开始遍历
cout<<dp[1][q];//在1节点,留q个树枝,最多能留住的苹果数
return 0;
}
总结
这是一道很模板的树上背包,但由于是树枝(边)上有价值,所以要细心改动一些,在细节上也要小心
很像选课这道题,但除了边上有价值以外,这道题一定是一棵二叉树!!!所以只有两条边,没有选课麻烦,但这个通解总能行
一般来说,树形dp都是先统计子树的答案,再汇总到根来计算,如 等数组的统计,约等于记忆化,但更多是dp,从叶子往上递推(一般)
几个经典模型:
- 树上背包
- 最小覆盖集(点)
- 最大独立集
- 最小支配集(边)
树形dp题
- [P1122] 最大子树和(模板)
- [P2610] 旅游(树的直径的求法)
- [P1270] “访问”美术馆(输入……)
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...