P1051 · OFFICIAL SOLUTION

P1051 树的价值 官方题解

Gioush OJ · P1051 树的价值

树的价值

【题意简述】

给定一棵以 11 为根的树。每次按照某个顺序探索一个尚未探索的叶子,从根到该叶子的路径上新出现的结点构成一条链。若这条链的最高结点为 tt、长度为 ll,则产生价值 atbla_tb_l。求遍历全部叶子时能够得到的最大总价值。

【数据点 1∼21\sim 21∼2】

【题目描述】

这一档保证 n7n\leq 7

【Hint】

枚举全部叶子的排列,并按照题意模拟探索过程。

【解法】

用标记数组维护已经探索的结点。加入一个叶子时,从它向根跳父亲,直到遇到第一个已经探索的结点,再由新结点数量与其中最高结点的权值计算本次价值。

若叶子数量为 kk,时间复杂度为 O(k!kn)\mathcal{O}(k!kn)

【数据点 3∼63\sim 63∼6】

【题目描述】

叶子数量允许使用状压动态规划。

【Hint】

已经探索的叶子集合确定后,所有根到叶路径的并集也随之确定。

【解法】

FSF_S 表示已经探索集合 SS 中所有叶子时的最大价值。枚举下一个叶子 vSv\notin S,直接求出加入 vv 时的新探索链,并作转移

FS{v}max(FS{v},FS+Value(S,v)).F_{S\cup\{v\}} \leftarrow \max\left(F_{S\cup\{v\}},F_S+\operatorname{Value}(S,v)\right).

这一做法仍然枚举叶子的探索先后,但它提示我们:真正影响价值的是每个结点第一次从哪个儿子方向被经过。

【正解】

【题目描述】

设计树形动态规划,消去叶子排列这一维。

【Hint】

一个探索序列会把全部结点划分成若干条从上向下的链。处理完结点 uu 的子树以后,只有一条从 uu 开始的链可能继续向祖先延伸。

【状态】

定义 du,ld_{u,l} 表示:

  • 已经确定 uu 子树内的探索顺序;
  • 其中有一条从 uu 开始、长度为 ll 的链暂时不计算价值,等待连接到 uu 的祖先;
  • 除这条链外,其余新探索链均已结算时能够得到的最大总价值。

uu 为叶子,唯一的链只包含 uu,所以

du,1=0,d_{u,1}=0,

其余状态均为负无穷。

【转移】

对于 uu 的儿子 vv,先计算这棵子树完全在 vv 处封口时的最优价值

gv=max1lhv{dv,l+avbl}.g_v= \max_{1\leq l\leq h_v} \left\{ d_{v,l}+a_vb_l \right\}.

若选择儿子 vv 的保留链穿过 uu,原来长度为 ll 的链增长为 l+1l+1;其余儿子 ww 的子树全部在 ww 处封口。因此

du,l+1=maxvSon(u){dv,l+wSon(u)gwgv}.d_{u,l+1} = \max_{v\in\operatorname{Son}(u)} \left\{ d_{v,l} +\sum_{w\in\operatorname{Son}(u)}g_w-g_v \right\}.

不可达状态必须设为负无穷,不能用 00 代替,因为 au,bla_u,b_l 均可能为负数。

【答案与正确性】

根结点没有祖先,最后一条保留链必须在根处结算,因此答案为

max1lh1{d1,l+a1bl}.\max_{1\leq l\leq h_1} \left\{ d_{1,l}+a_1b_l \right\}.

对任意探索序列,在每个非叶结点 uu 处,只有第一个被探索的儿子方向能够把链继续向上延伸,故它必然对应某次转移。

反过来,先探索被选中儿子中的保留链,再依次完成其余儿子的最优探索顺序,就能实现转移给出的链划分与价值。因此上述状态恰好覆盖全部合法探索序列。

【复杂度分析】

huh_uuu 子树的高度。每个非根结点的动态规划数组只会在处理父亲时完整扫描常数次,因此时间复杂度为

O(u=1nhu)O(nm).\mathcal{O}\left(\sum_{u=1}^{n}h_u\right) \leq \mathcal{O}(nm).

空间复杂度同样为 O(uhu)O(nm)\mathcal{O}\left(\sum_uh_u\right)\leq\mathcal{O}(nm)

【参考代码】

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