前言
- 给定一个 n 个结点 n-1 条边的树,让你求树中满足某条件的最优解。
- 一般递归(小的子树到大的子树)的顺序作为 DP 的阶段
- DP 状态表示:一般第一维是结点的编号,代表以该结点为根的子树,采用递归的形式进行转移
- 对于每个节点 u ,先递归在它的每个子结点 v 上进行 DP,回溯的时候,再让子结点 v 向 u 进行状态转移
树上背包
套路
- 线性背包: 就是在前 i-1 个物品和第 i 个物品中权衡取最优值。比如第 i 个物品取与不取,取多少个等等
- 树型背包: 就是在不包括当前 v 的子树和当前 v 的子树中权衡取最优值。比如当前 v 的子树中取与不取,取多少个等等
模板代码如下:
void dfs(int u,int fa) {
//初始值
for(int i=vex[u]; i; i=e[i].next) {
int v=e[i].v;
dfs(v,fa);
if(v==fa)continue;
for(int j=up;j>=0;j--){//up为上届
for(int k=0;k<=j;k++){
f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]);
}
}
}
}
例题1:树型背包模板题1
- 题目描述: n 个点构成一棵树,1 号点为网络中心,[n-m+1,n] 号点是人。每条边有边权 wi ,表示如果从上往下经过这条边,则需要花费 wi 。每个人都有 ai ,表示如果网络覆盖到点 i ,则网络中心会收益 ai 。问网络中心在不亏本的情况下,覆盖到的人最多。
- 状态设置: f [ u ] [ j ] f[u][j] f[u][j] :如果在u及其子树中选择 j 个人,最多收益多少钱。。
- 转移方程: 在子树 v 中选择 k 个人,在 u 的不考虑子树的子树中,选择 j-k 人。 f [ u ] [ j ] = m i n ( f [ u ] [ j ] , f [ u ] [ j − k ] + f [ v ] [ k ] − e [ i ] . c ) f[u][j]=min(f[u][j],f[u][j-k]+f[v][k]-e[i].c) f[u][j]=min(f[u][j],f[u][j−k]+f[v][k]−e[i].c), k ∈ [ 0 , j ] k\in[0,j] k∈[0,j]
- 初始值: f [ u ] [ j ] = − 1 e 9 , j ∈ [ 1 , n ] f[u][j]=-1e9,j\in[1,n] f[u][j]=−1e9,j∈[1,n],若 u 点有人,则 f [ u ] [ 1 ] = a [ u ] f[u][1]=a[u] f[u][1]=a[u]
- 目标: f [ 1 ] [ j ] > = 0 , f[1][j]>=0, f[1][j]>=0,且 j 最大。
Code
#include<bits/stdc++.h>
using namespace std;
const int N=3005;
struct edge{
int v,w,next;
}e[N*2];
int vex[N],tot,a[N],f[N][N],size[N],n,m;
void add(int u,int v,int w){
tot++;
e[tot].v=v;
e[tot].w=w;
e[tot].next=vex[u];
vex[u]=tot;
}
void dfs(int u,int fa) {
if(a[u]){
size[u]=1;
f[u][1]=a[u];
}else{
f[u][1]=-1e9;
}
for(int j=2;j<=n;j++)f[u][j]=-1e9;
for(int i=vex[u]; i; i=e[i].next) {
int v=e[i].v;
if(v==fa)continue;
dfs(v,u);
size[u]+=size[v];
for(int j=size[u]; j>=1; j--) {
for(int k=0; k<=j; k++) {
f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]-e[i].w);
}
}
}
}
int main() {
int num,v,w;
cin>>n>>m;
for(int i=1;i<=n-m;i++){
cin>>num;
for(int j=1;j<=num;j++){
cin>>v>>w;
add(i,v,w);
add(v,i,w);
}
}
for(int i=n-m+1;i<=n;i++){
cin>>v;
a[i]=v;
}
dfs(1,0);
for(int j=n;j>=0;j--){
if(f[1][j]>=0){
cout<<j;
return 0;
}
}
return 0;
}
例题2:树型背包模板题2
- 题目描述: 现在有 n 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 a 是课程 b 的先修课即只有学完了课程 a,才能学习课程 b)。一个学生要从这些课程里选择 m 门课程学习,问他能获得的最大学分是多少?
- 问题分析: 很显然,这是一颗森林,或者说,这是一个多起点的有向无环图,我们添加一个虚拟课程0,来将其变成一棵树,或者说,一个单起点的有向无环图。然后我们就可以进行树型DP了
- 状态设置: f [ u ] [ j ] f[u][j] f[u][j]:表示在 u 及其子树中,选择 j 门课程,最多可以获得多少学分。
- 转移方程: 在子树 v 中选择 k 门课,在 u 的不考虑子树的子树中,选择 j-k 门课。 f [ u ] [ j ] = m i n ( f [ u ] [ j ] , f [ u ] [ j − k ] + f [ v ] [ k ] − e [ i ] . c ) f[u][j]=min(f[u][j],f[u][j-k]+f[v][k]-e[i].c) f[u][j]=min(f[u][j],f[u][j−k]+f[v][k]−e[i].c), k ∈ [ 0 , j ) k\in[0,j) k∈[0,j)(注意由于必须选择 u 才能选择 v 这颗子树中的课,所以 u 至少选一门,j-k>=1)
- 初始值: f [ u ] [ 1 ] = a [ u ] , f [ u ] [ j ] = − 1 e 9 , j ∈ [ 1 , n ] f[u][1]=a[u],f[u][j]=-1e9,j\in[1,n] f[u][1]=a[u],f[u][j]=−1e9,j∈[1,n]
- 目标: f [ 0 ] [ m + 1 ] f[0][m+1] f[0][m+1]
Code
#include<bits/stdc++.h>
using namespace std;
const int N=3005;
struct edge{
int v,next;
}e[N*2];
int vex[N],tot,a[N],f[N][N],n,m,size[N];
void add(int u,int v){
tot++;
e[tot].v=v;
e[tot].next=vex[u];
vex[u]=tot;
}
void dfs(int u) {
size[u]=1;
f[u][1]=a[u];
for(int j=2;j<=n;j++)f[u][j]=-1e9;
for(int i=vex[u]; i; i=e[i].next) {
int v=e[i].v;
dfs(v);
size[u]+=size[v];
for(int j=size[u];j>=0;j--){
for(int k=0;k<j;k++){
f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]);
}
}
}
}
int main() {
int v;
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>v>>a[i];
add(v,i);
}
dfs(0);
cout<<f[0][m+1];
return 0;
}
例题3:
- 问题分析: 由于每个点可能是被子结点覆盖,也可能是被父节点覆盖,每个结点放置与不放置对周围结点的影响也不同,因此我们对每个点考虑设置四个状态
- 状态设置: f [ u ] [ j ] [ 0 ] [ 0 ] , f [ u ] [ j ] [ 0 ] [ 1 ] , f [ u ] [ j ] [ 1 ] [ 0 ] , f [ u ] [ j ] [ 1 ] [ 1 ] f[u][j][0][0],f[u][j][0][1],f[u][j][1][0],f[u][j][1][1] f[u][j][0][0],f[u][j][0][1],f[u][j][1][0],f[u][j][1][1] :u点没放置且u点没被子结点覆盖,u点没放置且u点被子结点覆盖,u点放置且u点没被子结点覆盖,u点放置且u点背子结点覆盖,对应下的方案数
- 转移方程:
- f [ u ] [ j ] [ 0 ] [ 0 ] = ∑ f [ u ] [ j − k ] f[u][j][0][0]=\sum f[u][j-k] f[u][j][0][0]=∑f[u][j−k]
例题4:物品价值的计算
- 题目描述: 给定一棵 n 个结点的有边权树和一个数字 k ,要求从 n 个结点中选出 k k k 个结点作为黑点,其他 n − k n-k n−k 个结点作为白点,使得这棵树值中两两黑点的距离之和与两俩白点的距离之和最大。输出这个最大值。
- 状态设置: f [ u ] [ j ] f[u][j] f[u][j]:从 u u u 为根的子树中,选出 j j j 个黑点的情况下的最大价值。
- 问题分析: 那么对于这一条边 w w w ,一共有黑色x黑色加白色x白色次经过。枚举子树 u u u 的黑色结点数量 j j j ,枚举子树 v v v 的黑色结点数量 p p p,那么子树 u u u 外的那棵子树的黑色结点数量为 n − s i z e [ u ] − s i z e [ v ] − ( k − j − p ) n-size[u]-size[v]-(k-j-p) n−size[u]−size[v]−(k−j−p) 。乘加一下即可。
void dfs(int u,int fa){
size[u]=1;
for(int i=vex[u];i;i=e[i].next){
int v=e[i].v;
if(v==fa)continue;
dfs(v,u);
for(int j=0;j<=k;j++)temp[j]=-1e9;
for(int j=0;j<=size[u];j++){
for(int p=0;p<=size[v];p++){
if(j+p>k)continue;
long long val1=1ll*(j*p+(size[u]-j)*(size[v]-p))*e[i].w;//与v这颗子树
long long val2=1ll*((k-j-p)*p+(n-size[u]-size[v]-(k-j-p))*(size[v]-p))*e[i].w;//与外面那颗子树
temp[j+p]=max(temp[j+p],f[u][j]+f[v][p]+val1+val2);
}
}
for(int j=0;j<=k;j++)f[u][j]=temp[j];
size[u]+=size[v];
}
}
相邻取与不取问题
套路
- 状态设置: 通常设置 f [ i ] [ 1 ] , f [ i ] [ 0 ] f[i][1],f[i][0] f[i][1],f[i][0]来表示第 i 个点的子树中,第 i 个点取某种状态下的最优值
例题1:相邻染色不染同
- 题目描述: n 个点的一棵树,每个点有三种染色方法012,相邻点不能选取同一颜色,求染0色的数量的最大值。
- 状态设置: f [ i ] [ 0 ] , f [ i ] [ 1 ] , f [ i ] [ 2 ] f[i][0],f[i][1],f[i][2] f[i][0],f[i][1],f[i][2] 来表示第 i 个点的子树中,第 i 个点分别选取 012 下,染0色的最大值。
- 状态转移:
- f [ u ] [ 0 ] = ∑ m a x ( f [ v ] [ 1 ] , f [ v ] [ 2 ] ) + 1 f[u][0]=\sum max(f[v][1],f[v][2])+1 f[u][0]=∑max(f[v][1],f[v][2])+1
- f [ u ] [ 1 ] = ∑ m a x ( f [ v ] [ 0 ] . f [ v ] [ 2 ] ) f[u][1]=\sum max(f[v][0].f[v][2]) f[u][1]=∑max(f[v][0].f[v][2])
- f [ u ] [ 2 ] = ∑ m a x ( f [ v ] [ 0 ] , f [ v ] [ 1 ] ) f[u][2]=\sum max(f[v][0],f[v][1]) f[u][2]=∑max(f[v][0],f[v][1])
例题2:选点覆盖覆盖整棵树
- 题目描述: n 个点的一棵树,若选择点 u ,则 u 和与 u 直接相邻的点均可被覆盖到,求最小选择多少个点覆盖整棵树。
- 状态设置: f [ i ] [ 1 ] , f [ i ] [ 0 ] f[i][1],f[i][0] f[i][1],f[i][0] 来表示第 i 个点的子树中,第 i 个点选与不选的情况下,选点覆盖整棵子树的最小值。
- 状态转移:
- f [ u ] [ 0 ] = ∑ f [ v ] [ 1 ] f[u][0]=\sum f[v][1] f[u][0]=∑f[v][1]
- f [ u ] [ 1 ] = ∑ m i n ( f [ v ] [ 0 ] , f [ v ] [ 1 ] ) + 1 f[u][1]=\sum min(f[v][0],f[v][1])+1 f[u][1]=∑min(f[v][0],f[v][1])+1
例题3:树上最大独立点集
- 题目描述: n 个点的一棵树,选择若干个点,但相邻的位置不能同时选,求最多可以选择多少个点
- 状态设置: f [ i ] [ 1 ] , f [ i ] [ 0 ] f[i][1],f[i][0] f[i][1],f[i][0] 来表示第 i 个点的子树中,第 i 个点选与不选的情况下,最多可以选择多少个点。
- 状态转移:
- f [ u ] [ 0 ] = ∑ m a x ( f [ v ] [ 0 ] , f [ v ] [ 1 ] ) f[u][0]=\sum max(f[v][0],f[v][1]) f[u][0]=∑max(f[v][0],f[v][1])
- f [ u ] [ 1 ] = ∑ f [ v ] [ 0 ] + 1 f[u][1]=\sum f[v][0]+1 f[u][1]=∑f[v][0]+1
例题4:树上最大权独立点集
- 题目描述: n 个点的一棵树,结点带权(正整数)。选择若干个点,但相邻的位置不能同时选。求最多可以选择多少个点,以及选择最多点的情况下的最小点权和,并判断该情况下:每个点是否被选择。
- 状态设置:
- f [ i ] [ 1 ] , f [ i ] [ 0 ] f[i][1],f[i][0] f[i][1],f[i][0] 来表示第 i i i 个点的子树中,第 i 个点选与不选的情况下,最多可以选择多少个点。
- g [ i ] [ 1 ] , f [ i ] [ 1 ] g[i][1],f[i][1] g[i][1],f[i][1] 来表示第 i i i 个点的子树中,选择最多个点的情况下,的最小点权和
- g g g 只需要跟着 f f f 转移即可
void dfs(int u,int fa){
long long sumval=0,sumnode=0;
for(int v:G[u]){
if(v==fa)continue;
dfs(v,u);
sumnode+=f[v][0];
sumval+=g[v][0];
if(f[v][1]>f[v][0]||(f[v][1]==f[v][0]&&g[v][1]<g[v][0])){
f[u][0]+=f[v][1];
g[u][0]+=g[v][1];
}else{
f[u][0]+=f[v][0];
g[u][0]+=g[v][0];
}
}
f[u][1]++;
g[u][1]+=a[u];
}
- 如何判断哪个点是否选择呢???
- 根结点的情况是已知的,从根结点出发再 d f s dfs dfs 一次
- 若 u u u 被选择,则 v v v 一定不被选择
- 若 u u u 不被选择,则根据 v v v 的 f , g f,g f,g 判断是否 v v v 被选择
void count(int u,int fa,int select){
if(select==1)ans[u]=select;
for(int v:G[u]){
if(v==fa)continue;
if(select==1)count(v,u,0);
else{
if(f[v][1]>f[v][0]||(f[v][1]==f[v][0]&&g[v][1]<g[v][0]))count(v,u,1);
else count(v,u,0);
}
}
}
例题5:任意结点子树AB点数量相同,最多几个 A 点
- 题目描述: n n n 个点的一棵树,每个点可以放一个 A ,要么放一个 B ,要么不放。当任意非叶子节点的 AB 点数量一样的情况下,最多可以放多少个 A 点。
- 状态设置: f [ i ] f[i] f[i] 为以 i i i 为根的子树 A 点数量最多为多少。
- 转移方程: f [ u ] = s u m ( f [ v ] ) + ( s i z e + 1 ) / 2 f[u]=sum(f[v])+(size+1)/2 f[u]=sum(f[v])+(size+1)/2, v v v 为非叶子结点, s i z e size size 为叶子结点数量。
树上换根法
套路
- 这样的题往往要求找到以某个点根,使得某条件最优。假设 u 与 v 相邻,其通常可以通过求出了以 u 点为根的值来快速求出以 v 点为根值。
- 通常我们会先 dfs 一遍求出以 1 为根的情况,以及一些相关值的预处理。然后再进入一遍 dfs 转移维护出所有点的情况。
例题1:树上选最优点问题
- 题目描述: 给定 n 个点的一棵树,定义 d i s ( u , v ) dis(u,v) dis(u,v)为 u 和 v 两点的距离。请你找到一个点 u ,使得 f ( u ) = ∑ i = 1 n d i s ( u , i ) f(u)=\sum_{i=1}^n dis(u,i) f(u)=∑i=1ndis(u,i) 最小化
- 问题分析: 对于每个点,都 dfs 显然是不行的,我们观察以 u 为根与以 v 为根有什么转移关系
- 转移方程: f [ v ] = f [ u ] + s i z e [ 1 ] − s i z e [ v ] − s i z e [ v ] f[v]=f[u]+size[1]-size[v]-size[v] f[v]=f[u]+size[1]−size[v]−size[v],其中 size[u] 为以 u 为根的子树大小
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e6+100;
struct node{
int u,v,next;
}e[N*2];
int vex[N],size[N],k,f[N],dep[N];
void add(int u,int v){
k++;
e[k].u=u;
e[k].v=v;
e[k].next=vex[u];
vex[u]=k;
}
void pre_dfs(int u,int fa){
size[u]=1;
for(int i=vex[u];i;i=e[i].next){
int v=e[i].v;
if(v==fa)continue;
dep[v]=dep[u]+1;
pre_dfs(v,u);
size[u]+=size[v];
}
}
void dfs(int u,int fa){
for(int i=vex[u];i;i=e[i].next){
int v=e[i].v;
if(v==fa)continue;
f[v]=f[u]-size[v]+size[1]-size[v];
dfs(v,u);
}
}
int main() {
int n,u,v;
cin>>n;
for(int i=1;i<n;i++){
cin>>u>>v;
add(v,u);
add(u,v);
}
pre_dfs(1,0);
for(int i=1;i<=n;i++)f[1]+=dep[i];
dfs(1,0);
int ans=1;
for(int i=2;i<=n;i++)if(f[i]>f[ans])ans=i;
cout<<ans;
return 0;
}
例题2:树上选最优点问题2
- 题目描述: 给定 n 个点的一棵树,定义 d i s ( u , v ) dis(u,v) dis(u,v)为 u 和 v 两点的距离,每个点还有一个权值 ai。请你找到一个点 u ,使得 f ( u ) = ∑ i = 1 n d i s ( u , i ) × a i f(u)=\sum_{i=1}^n dis(u,i)\times a_i f(u)=∑i=1ndis(u,i)×ai 最大化
- 转移方程: f [ v ] = f [ u ] + s i z e [ 1 ] − s i z e [ v ] − s i z e [ v ] f[v]=f[u]+size[1]-size[v]-size[v] f[v]=f[u]+size[1]−size[v]−size[v],其中 size[u] 为以 u 为根的子树的 ∑ a i \sum a_i ∑ai
例题3:树上判断重心
- 题目描述: 给定 n 个点的一棵树,你有一次将树改造的机会,改造的意思是删去一条边,再加入一条边,保证改造后还是一棵树。请问有多少点可以通过改造,成为这颗树的重心?(重心:以重心为根的最大子树不大于 n 2 \frac{n}{2} 2n
朴素树型 dp
例题1:树的重心
- 题目描述: 给定一棵树,求树的重心。重心的定义:以重心为根的树的最大子树大小小于等于 [ n 2 ] [\frac{n}{2}] [2n]
- 问题分析: s i z e [ u ] size[u] size[u] 表示以 u 为根的子树大小。那么以 u 为根的树的最大子树大小为 m a x ( m a x ( s i z e [ v ] ) , n − s i z e [ u ] ) max(max(size[v]),n-size[u]) max(max(size[v]),n−size[u]) 。判断一下这个值是否小于等于 [ n 2 ] [\frac{n}{2}] [2n] 即可。
void dfs(int u,int fa) {
size[u]=1;
int temp=0;
for(int i=vex[u]; i; i=e[i].next) {
int v=e[i].v;
if(v==fa)continue;
dfs(v,u);
size[u]+=size[v];
temp=max(size[v],temp);
}
temp=max(temp,n-size[u]);
if(temp<=n/2)zhon.push(u);
}
- 可以证明:一棵树的重心有一到两个。
例题2:树的直径
- 题目描述: 给定一棵树,求树的直径。直径的定义:树上最远距离。
- 问题分析:
例题3:树的半径
例题4:添加边权使 S 到叶子距离相等
- 题目描述: n 个点,s 为根,构成一棵树,有边权。你可以添加边权值。问你最少添加多少的边权值和,使得 S 到所有叶子结点的距离相等
- 问题分析: 设 f [ u ] f[u] f[u] 为以 u u u 根的子树中,要让 u u u 到其所有叶子节点距离最小所需要添加的边权值和。显然要让 u u u 到所有叶子的距离都等于 maxdis( u 到叶子的最远距离)。
- 转移方程: f [ u ] = ∑ f [ v ] + ∑ ( m a x d i s − d i s ) f[u]=\sum f[v]+\sum (maxdis-dis) f[u]=∑f[v]+∑(maxdis−dis)
#include<bits/stdc++.h>
using namespace std;
const int N=2e6+100;
long long f[N],dis[N],siz[N];
struct E{
int u,v,w,next;
}e[N*2];
int vex[N],tot;
void add(int u,int v,int w){
tot++;
e[tot].u=u;
e[tot].v=v;
e[tot].w=w;
e[tot].next=vex[u];
vex[u]=tot;
}
void dfs(int u,int fa){
for(int i=vex[u];i;i=e[i].next){
int v=e[i].v;
if(v==fa)continue;
dfs(v,u);
f[u]+=f[v];
if(dis[u]==0)dis[u]=dis[v]+e[i].w;
else{
if(dis[u]>dis[v]+e[i].w)f[u]+=dis[u]-(dis[v]+e[i].w);
}else{
f[u]+=(dis[v]+e[i].w-dis[u])*siz[u];
dis[u]=dis[v]+e[i].w;
}
}
siz[u]++;
}
}
int main() {
int n,s,u,v,w;
cin>>n>>s;
for(int i=1;i<n;i++){
cin>>u>>v>>w;
add(u,v,w);
add(v,u,w);
}
dfs(s,0);
cout<<f[s];
return 0;
}
例题5:删一条边添一条边,使得树直径最小
- 题目描述: n 个点构成一棵树,有边权。删除一条边再添加一条边,使得该树中最大距离最小。 n ∈ [ 1 , 5000 ] n\in[1,5000] n∈[1,5000]
- 问题分析:
- 枚举要删的边,将一棵树拆成两棵树,再添加一条边使其变回一棵树
- 考虑最大距离会在哪?
- 左树直径,右树直径:这两个距离是不会因添加边而影响的
- 跨越左右树的一条路径:如何添加使得这条最大路径最小呢?将两棵树直径的中点在一起,最大距离为
m
a
x
d
i
s
1
+
m
a
x
d
i
s
2
maxdis1+maxdis2
maxdis1+maxdis2 。
例题6:倒推设状态
- 题目描述:
- n-1 个城市和 n 个农村构成一棵树,城市 1 号点为根,农村为该树的叶子结点。每个叶子结点都有权值 a i , b i , c i a_i,b_i,c_i ai,bi,ci。对于每个非叶子结点,其左右的两条路径中可翻新一条路径。
- 设 x x x 为叶子 i i i 到根结点的未被翻新的左边路数量, y y y 为叶子 i i i 到根结点未被翻新的右边路数量。则总不方便值: ∑ i = 1 n c i × ( a i + x ) × ( b i + y ) \sum_{i=1}^n c_i\times (a_i+x)\times (b_i+y) ∑i=1nci×(ai+x)×(bi+y) 。
- 请最小化这个总不方便值 。 n < = 2 e 4 , a i , b i ∈ [ 1 , 60 ] n<=2e4,a_i,b_i\in[1,60] n<=2e4,ai,bi∈[1,60]
- 问题分析: 考虑倒推。设 f [ u ] [ i ] [ j ] f[u][i][j] f[u][i][j] 表示从 u 到根结点有 i 条未被翻新左边路 ,j 条未被翻新右边路,时的最小总不方便值。
- 对于农村(叶子结点), f [ u ] [ i ] [ j ] = c u × ( a u + u ) × ( b u + j ) f[u][i][j]=c_u\times(a_u+u)\times(b_u+j) f[u][i][j]=cu×(au+u)×(bu+j)
- 对于城市(非叶子结点), f [ u ] [ i ] [ j ] = m i n ( f [ l s o n ] [ i ] [ j ] + f [ r s o n ] [ i ] [ j + 1 ] , f [ l s o n ] [ i + 1 ] [ j ] + f [ r s o n ] [ i ] [ j ] ) f[u][i][j]=min(f[lson][i][j]+f[rson][i][j+1],f[lson][i+1][j]+f[rson][i][j]) f[u][i][j]=min(f[lson][i][j]+f[rson][i][j+1],f[lson][i+1][j]+f[rson][i][j])。分别表示翻新左边路和翻新右边路。
- 目标状态: f [ 1 ] [ 0 ] [ 0 ] f[1][0][0] f[1][0][0]
奉上 MLE 代码
#include<bits/stdc++.h>
using namespace std;
const int N=4e4+100;
long long f[N][62][62];
int a[N],b[N],c[N],n;
struct node{
int l,r;
}tr[N];
void dfs(int u){
if(u>n){
for(int i=0;i<=61;i++){
for(int j=0;j<=61;j++){
f[u][i][j]=1ll*c[u]*(a[u]+i)*(b[u]+j);
}
}
return;
}
dfs(tr[u].l);
dfs(tr[u].r);
for(int i=0;i<=60;i++){
for(int j=0;j<=60;j++){
f[u][i][j]=min(f[tr[u].l][i+1][j]+f[tr[u].r][i][j],f[tr[u].l][i][j]+f[tr[u].r][i][j+1]);
}
}
}
int main() {
cin>>n;
for(int i=1;i<n;i++){
cin>>tr[i].l>>tr[i].r;
if(tr[i].l<0)tr[i].l=-tr[i].l+n;
if(tr[i].r<0)tr[i].r=-tr[i].r+n;
}
for(int i=1;i<=n;i++)cin>>a[i+n]>>b[i+n]>>c[i+n];
dfs(1);
cout<<f[1][0][0];
return 0;
}
优化空间:
- 当 u u u 的 d p dp dp 值被求完之后, l s o n , r s o n lson,rson lson,rson 的 d p dp dp 值就无用了。
- 考虑记录点的 d f n dfn dfn 次序。左子树跑 d f n [ u ] + 1 dfn[u]+1 dfn[u]+1,右子树跑 d f n [ u ] + 2 dfn[u]+2 dfn[u]+2。跑完之后,刚好 d f n [ u ] + 1 dfn[u]+1 dfn[u]+1 和 d f n [ u ] + 2 dfn[u]+2 dfn[u]+2 的均存在,刚好可以取求 u u u 的 d p dp dp 值。根据题目描述: d f n dfn dfn 值不会 80 .
#include<bits/stdc++.h>
using namespace std;
const int N=4e4+100;
long long f[100][62][62];
int a[N],b[N],c[N],n,dfn[N];
struct node{
int l,r;
}tr[N];
void dfs(int u,int cnt){
dfn[u]=cnt;
if(u>n){
for(int i=0;i<=61;i++){
for(int j=0;j<=61;j++){
f[dfn[u]][i][j]=1ll*c[u]*(a[u]+i)*(b[u]+j);
}
}
return;
}
dfs(tr[u].l,dfn[u]+1);
dfs(tr[u].r,dfn[u]+2);
for(int i=0;i<=60;i++){
for(int j=0;j<=60;j++){
f[dfn[u]][i][j]=min(f[dfn[tr[u].l]][i+1][j]+f[dfn[tr[u].r]][i][j],f[dfn[tr[u].l]][i][j]+f[dfn[tr[u].r]][i][j+1]);
}
}
}
int main() {
cin>>n;
for(int i=1;i<n;i++){
cin>>tr[i].l>>tr[i].r;
if(tr[i].l<0)tr[i].l=-tr[i].l+n;
if(tr[i].r<0)tr[i].r=-tr[i].r+n;
}
for(int i=1;i<=n;i++)cin>>a[i+n]>>b[i+n]>>c[i+n];
dfs(1,1);
cout<<f[dfn[1]][0][0];
}