树上动态规划的归纳总结

前言

  • 给定一个 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][jk]+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][jk]+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][jk]

例题4:物品价值的计算

例题链接

  • 题目描述: 给定一棵 n 个结点的有边权树和一个数字 k ,要求从 n 个结点中选出 k k k 个结点作为黑点,其他 n − k n-k nk 个结点作为白点,使得这棵树值中两两黑点的距离之和与两俩白点的距离之和最大。输出这个最大值。
  • 状态设置: 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) nsize[u]size[v](kjp) 。乘加一下即可。
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]),nsize[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]+(maxdisdis)
#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];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值