P1051 树的价值 官方题解
Gioush OJ · P1051 树的价值
树的价值
【题意简述】
给定一棵以 为根的树。每次按照某个顺序探索一个尚未探索的叶子,从根到该叶子的路径上新出现的结点构成一条链。若这条链的最高结点为 、长度为 ,则产生价值 。求遍历全部叶子时能够得到的最大总价值。
【数据点 1∼21\sim 21∼2】
【题目描述】
这一档保证 。
【Hint】
枚举全部叶子的排列,并按照题意模拟探索过程。
【解法】
用标记数组维护已经探索的结点。加入一个叶子时,从它向根跳父亲,直到遇到第一个已经探索的结点,再由新结点数量与其中最高结点的权值计算本次价值。
若叶子数量为 ,时间复杂度为 。
【数据点 3∼63\sim 63∼6】
【题目描述】
叶子数量允许使用状压动态规划。
【Hint】
已经探索的叶子集合确定后,所有根到叶路径的并集也随之确定。
【解法】
令 表示已经探索集合 中所有叶子时的最大价值。枚举下一个叶子 ,直接求出加入 时的新探索链,并作转移
这一做法仍然枚举叶子的探索先后,但它提示我们:真正影响价值的是每个结点第一次从哪个儿子方向被经过。
【正解】
【题目描述】
设计树形动态规划,消去叶子排列这一维。
【Hint】
一个探索序列会把全部结点划分成若干条从上向下的链。处理完结点 的子树以后,只有一条从 开始的链可能继续向祖先延伸。
【状态】
定义 表示:
- 已经确定 子树内的探索顺序;
- 其中有一条从 开始、长度为 的链暂时不计算价值,等待连接到 的祖先;
- 除这条链外,其余新探索链均已结算时能够得到的最大总价值。
若 为叶子,唯一的链只包含 ,所以
其余状态均为负无穷。
【转移】
对于 的儿子 ,先计算这棵子树完全在 处封口时的最优价值
若选择儿子 的保留链穿过 ,原来长度为 的链增长为 ;其余儿子 的子树全部在 处封口。因此
不可达状态必须设为负无穷,不能用 代替,因为 均可能为负数。
【答案与正确性】
根结点没有祖先,最后一条保留链必须在根处结算,因此答案为
对任意探索序列,在每个非叶结点 处,只有第一个被探索的儿子方向能够把链继续向上延伸,故它必然对应某次转移。
反过来,先探索被选中儿子中的保留链,再依次完成其余儿子的最优探索顺序,就能实现转移给出的链划分与价值。因此上述状态恰好覆盖全部合法探索序列。
【复杂度分析】
记 为 子树的高度。每个非根结点的动态规划数组只会在处理父亲时完整扫描常数次,因此时间复杂度为
空间复杂度同样为 。
【参考代码】
/*Author:EhundateghDate:2026/7/24Name:tree.cppYou steal,I kill.*/#include <vector>#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 8010#define MAXM 810using namespace std;const long long INF=4e18;int T,n,m,Fa[MAXN],Height[MAXN];long long a[MAXN],b[MAXN],Dp[MAXN][MAXM],Best[MAXN];vector <int> Son[MAXN];void Solve(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) Son[i].clear(); for(int i=2;i<=n;i++){ scanf("%d",&Fa[i]); Son[Fa[i]].push_back(i); } for(int i=1;i<=n;i++) scanf("%lld",&a[i]); for(int i=1;i<=n;i++) scanf("%lld",&b[i]); memset(Dp,0x80,sizeof(Dp)); for(int Now=n;Now>=1;Now--){ if(Son[Now].empty()){ Height[Now]=1; Dp[Now][1]=0; continue; } Height[Now]=1; long long Sum=0; for(int To:Son[Now]){ Height[Now]=max(Height[Now],Height[To]+1); Best[To]=-INF; for(int j=1;j<=Height[To];j++){ Best[To]=max(Best[To],Dp[To][j]+a[To]*b[j]); } Sum+=Best[To]; } for(int To:Son[Now]){ for(int j=1;j<=Height[To];j++){ Dp[Now][j+1]=max(Dp[Now][j+1],Dp[To][j]+Sum-Best[To]); } } } long long Ans=-INF; for(int i=1;i<=Height[1];i++) Ans=max(Ans,Dp[1][i]+a[1]*b[i]); printf("%lld\n",Ans); return;}int main(){ int c; scanf("%d%d",&c,&T); while(T-->0) Solve(); return 0;}