[笔记]树形 DP 刷题记录

P11294 [NOISG 2022 Qualification] Tree Cutting

Tag:树的直径,杂项。

删除一条边,会产生两棵树,此时最优就是将两树的直径端点连起来,答案为两树直径之和再\({}+1\)

因此考虑枚举删除的边 \((u,v)\),不妨令 \(u\) 为父节点。我们需要知道:

  • 子树 \(v\) 的直径。
  • 原树删除子树 \(v\) 后的直径。

前者只需要按正常的树形 DP,记录每个节点能向下延伸的最长、次长链(\(mx[u][0/1]\))即可求得。

后者的答案相当于在下面三项中选两项:

  • 删去子树 \(v\) 后,从 \(u\) 向下延伸的最长链。
  • 删去子树 \(v\) 后,从 \(u\) 向下延伸的次长链。
  • \(u\) 向子树 \(u\) 外延伸的最长链。

第三项可以采用 \(2\) 次 DFS 的方法,一次从下往上,一次从上往下(即 P10962)。

前两项可以额外维护 \(u\) 能向下延伸的第 \(3\) 长链(\(mx[u][2]\))。

  • \(mx[v][0]+1=mx[u][0]\),则删除 \(v\) 后的最长链、次长链即为 \(mx[u][1],mx[u][2]\)
  • \(mx[v][0]+1=mx[u][1]\),则删除 \(v\) 后的最长链、次长链即为 \(mx[u][0],mx[u][2]\)
  • 其他情况,删除 \(v\) 后的最长链、次长链即为 \(mx[u][0],mx[u][1]\)

时间复杂度 \(O(n)\)

点击查看代码
#include<bits/stdc++.h>
#define eb emplace_back
using namespace std;
const int N=3e5+5;
int n,mx[N][3],g[N],f[N],ans;//mx:子树内前k大的距离  g:子树内的直径  f:子树外最大的距离 
inline void merge(int a[3],int w){
	if(w>=a[0]) a[2]=a[1],a[1]=a[0],a[0]=w;
	else if(w>=a[1]) a[2]=a[1],a[1]=w;
	else if(w>=a[2]) a[2]=w;
}
vector<int> G[N];
inline void dfs1(int u,int fa){
	for(int i:G[u]){
		if(i^fa){
			dfs1(i,u);
			merge(mx[u],mx[i][0]+1);
			g[u]=max(g[u],g[i]);
		}
	}
	g[u]=max(g[u],mx[u][0]+mx[u][1]);
}
inline void dfs2(int u,int fa){
	for(int i:G[u]){
		if(i^fa){
			if(mx[i][0]+1==mx[u][0]) f[i]=max(f[u],mx[u][1])+1;
			else f[i]=max(f[u],mx[u][0])+1;
			dfs2(i,u);
			ans=max(ans,g[i]+1+(
				mx[i][0]+1==mx[u][0]?mx[u][1]+max(f[u],mx[u][2]):
				mx[i][0]+1==mx[u][1]?mx[u][0]+max(f[u],mx[u][2]):
				mx[u][0]+max(f[u],mx[u][1])
			));
		}
	}
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); 
	cin>>n;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		G[u].eb(v),G[v].eb(u);
	}
	dfs1(1,0),dfs2(1,0);
	cout<<ans<<"\n";
	return 0;
}

P3177 [HAOI2015] 树上染色

Tag:树上背包。

下文记 \(m\) 为黑点的数量。

直接计算全局贡献不好做,考虑只记录子树中的贡献。

具体来说,令 \(f[u][j]\) 为子树 \(u\) 中,将 \(j\) 个点染成黑色,子树内的最大答案。

转移时,用类似树上背包的方式,将已 \(u\) 和合并过的子节点视作一个泛化物品,则有转移:

\[f[u][j]\gets \max_k f[u][j-k]+f[i][k]+\color{#9900aa}w\times k\times (m-k)\color{none}+\color{#00aa99} w\times (siz_i-k)\times (n-m-(siz_i-k)) \]

时间复杂度 \(O(nk)\),注意上下界优化,否则会假掉。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
using namespace std;
const int N=2005,M=2005;
int n,m,siz[N],f[N][M];
struct Ed{int to,w;};
vector<Ed> G[N];
inline void dfs(int u,int fa){
	siz[u]=1;
	for(Ed i:G[u]){
		int v=i.to,w=i.w;
		if(v==fa) continue;
		dfs(v,u);
		for(int j=min(m,siz[u]+siz[v]);~j;j--){
			for(int k=max(0ll,j-siz[u]);k<=min(siz[v],j);k++){
				f[u][j]=max(f[u][j],f[u][j-k]+f[v][k]+(k*(m-k)+(siz[v]-k)*(n-m-siz[v]+k))*w);
			}
		}
		siz[u]+=siz[v];
	}
}
signed main(){
	cin>>n>>m;
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		G[u].eb(Ed{v,w});
		G[v].eb(Ed{u,w});
	}
	dfs(1,0);
	cout<<f[1][m]<<"\n";
	return 0;
}

CF815C Karen and Supermarket ~ Codeforces

Tag:树上背包。
Trick:互换维度。

考虑树上背包,但是 \(m\) 太大了。

一个常用的技巧是互换维度,令 \(f[u][j][0/1]\) 为子树 \(u\) 中恰好买 \(j\) 个东西,\(u\) 是/否使用优惠券,所需要的最小价值。

则有转移:

\[f[u][j][0]\gets\min_k f[u][j-k][0]+f[i][k][0]\\ f[u][j][1]\gets\min_k f[u][j-k][0]+\min(f[i][k][0],f[i][k][1]) \]

使用上下界优化,时间复杂度 \(O(n^2)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define eb emplace_back
using namespace std;
const int N=5005;
int n,m,f[N][N][2],u[N],v[N],siz[N];
//f[u][j][k]:子树u中恰好买j个物品,u是/否使用优惠券,最小花费 
vector<int> G[N];
inline void chmn(int &x,int y){x=min(x,y);}
inline void dfs(int u){
	siz[u]=1;
	for(int i:G[u]){
		dfs(i);
		for(int j=min(n,siz[u]+siz[i]);j;j--){
			for(int k=max(0ll,j-siz[u]);k<=min(siz[i],j);k++){
				chmn(f[u][j][1],f[u][j-k][1]+min(f[i][k][0],f[i][k][1]));
				chmn(f[u][j][0],f[u][j-k][0]+f[i][k][0]);
			}
		}
		siz[u]+=siz[i];
	}
}
signed main(){
	memset(f,0x3f,sizeof f);
	cin>>n>>m;
	for(int i=1,u,v,x;i<=n;i++){
		cin>>u>>v;
		f[i][0][0]=0;//不买不能使优惠券,所以f[i][0][1]=inf 
		f[i][1][0]=u,f[i][1][1]=u-v;
		if(i>1) cin>>x,G[x].eb(i);
	}
	dfs(1);
	for(int i=n;~i;i--)
		if((f[1][i][0]<=m)||(f[1][i][1]<=m))
			cout<<i<<"\n",exit(0);
	return 0;
}
posted @ 2025-11-14 15:52  Sinktank  阅读(5)  评论(0)    收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2025 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.