YBTOJ 树形dp合集

qwq

树上求和

f [ x ] [ 0 ] f[x][0] f[x][0] 为不选 x x x x x x 及其子树所能得到的最大权值, f [ x ] [ 1 ] f[x][1] f[x][1] 为选 x x x x x x 及子树所能得到的最大权值,根据选了儿子就不能选父亲的规则转移一下即可。

结点覆盖

f [ x ] [ 0 ] f[x][0] f[x][0] f [ x ] [ 1 ] f[x][1] f[x][1] f [ x ] [ 2 ] f[x][2] f[x][2] 分别表示在 x x x 及其子树内,使结点 x x x 被父亲、自己、儿子覆盖所需的最小花费,则有:

  • x x x 被父亲覆盖,儿子要么被自己覆盖要么被儿子的儿子覆盖,即 f [ x ] [ 0 ] + = min ⁡ ( f [ v ] [ 1 ] , f [ v ] [ 2 ] ) f[x][0]+=\min(f[v][1],f[v][2]) f[x][0]+=min(f[v][1],f[v][2])
  • x x x 被自己覆盖,儿子三种情况均可,即 f [ x ] [ 1 ] + = min ⁡ ( f [ v ] [ 0 ] , m i n ( f [ v ] [ 1 ] , f [ v ] [ 2 ] ) ) f[x][1]+=\min(f[v][0],min(f[v][1],f[v][2])) f[x][1]+=min(f[v][0],min(f[v][1],f[v][2]))
  • x x x 被儿子覆盖,至少有一个儿子要被自己覆盖,其余儿子要么被自己覆盖要么被儿子的儿子覆盖,即 f [ x ] [ 2 ] + = min ⁡ ( f [ v ] [ 1 ] , f [ v ] [ 2 ] ) f[x][2]+=\min(f[v][1],f[v][2]) f[x][2]+=min(f[v][1],f[v][2]),当对于每个 v v v 都取 f [ v ] [ 2 ] f[v][2] f[v][2] 的时候是不合法的,所以要额外记录一个值 m i n n = min ⁡ { f [ v ] [ 1 ] − f [ v ] [ 2 ] } minn=\min\{f[v][1]-f[v][2]\} minn=min{f[v][1]f[v][2]}
inline void dfs(int x){
	f[x][1]=a[x];
	int flag=0,minn=1e9;
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		dfs(v);
		f[x][0]+=min(f[v][1],f[v][2]);
		f[x][1]+=min(f[v][0],min(f[v][1],f[v][2]));
		if(f[v][1]<=f[v][2]) flag=1,f[x][2]+=f[v][1];
		else f[x][2]+=f[v][2],minn=min(minn,f[v][1]-f[v][2]);
	}
	if(!flag) f[x][2]+=minn;
}

最长距离

到每个结点距离最远的点,要么在它的子树内,要么在它的子树外。

根据这个性质,设 f [ x ] [ 0 ] f[x][0] f[x][0] f [ x ] [ 2 ] f[x][2] f[x][2] 分别为 x x x 的子树内和 x x x 的子树外的点到 x x x 的最远距离,那么一遍dfs可以很容易求出 f [ x ] [ 0 ] f[x][0] f[x][0],求 f [ x ] [ 2 ] f[x][2] f[x][2] 也无非两种情况:最远点不在 x x x 的父亲的子树内,或最远点在 x x x 的父亲的子树内。设 x x x 到父亲的距离为 w w w,第一种情况的答案就是 w + f [ f a ] [ 2 ] w+f[fa][2] w+f[fa][2],第二种情况的答案就是 w + w+ w+不在 x x x 的子树内的点 f a fa fa 的距离最大值,有了不在同一子树的限制,就需要在第一次dfs时维护 f [ x ] [ 1 ] f[x][1] f[x][1] 作为 x x x 的子树内的点到 x x x 的次远距离。

inline void dfs(int x){
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v,w=e[i].w;
		dfs(v);
		if(f[v][0]+w>=f[x][0]) f[x][1]=f[x][0],f[x][0]=f[v][0]+w;
		else if(f[v][0]+w>f[x][1]) f[x][1]=f[v][0]+w;
	}
}
inline void dfs2(int x,int fa,int w){
	if(f[x][0]+w==f[fa][0]){
		f[x][2]=max(f[fa][2],f[fa][1])+w;
	}
	else f[x][2]=max(f[fa][2],f[fa][0])+w;
	for(int i=head[x];i;i=e[i].nxt) dfs2(e[i].v,x,e[i].w);
}

选课方案

树形dp经典问题但是我给忘了

但是可以现场胡:选课之间的依赖关系可以构成森林,建立一个虚拟结点 0 0 0 就可以把森林变成一棵以 0 0 0 为根的树,问题就变为在这棵树上选 m + 1 m+1 m+1 个点,选了父节点才能选子节点,问怎么选权值和最大。

f [ i ] [ j ] f[i][j] f[i][j] 为在以 i i i 为根的子树中选 j j j 个结点能选出的最大值,那么只需要dfs的时候枚举 j j j,再枚举每一个子节点 v v v 里选的点数 k k k 进行dp转移即可。时间复杂度 O ( n 3 ) O(n^3) O(n3)

inline void dfs(int x){
	f[x][1]=a[x];
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		dfs(v);
		for(int j=m+1;j>=2;--j) ff(k,1,j-1) f[x][j]=max(f[x][j],f[v][k]+f[x][j-k]);
		//因为期望用到的f[x][j-k]都是在不算当前节点v的情况下所获得的最大值,所以要倒着枚举j,保证f[x][j-k]还没被更新
	}
}

路径求和

正解是记录每条边会被计入答案多少次,好巧妙qwq

我的做法:对于每个点分别计算在以它为根的子树内和它的子树外的叶子结点到它的距离和,分别记为 f [ x ] f[x] f[x] g [ x ] g[x] g[x]

先求解 f [ x ] f[x] f[x],可以得到转移式 f [ x ] = ∑ v ∈ s o n x ( f [ v ] + n u m [ v ] × w ) f[x]=\sum_{v\in son_x} (f[v]+num[v]\times w) f[x]=vsonx(f[v]+num[v]×w),其中 n u m [ i ] num[i] num[i] 表示 i i i 的子树里的叶子结点数,可以事先递归预处理。

再求解 g [ x ] g[x] g[x],设 x x x 的父节点为 f a fa fa,那么 x x x 的子树外的叶子结点也有两种情况:在 f a fa fa 的子树外和在 f a fa fa 的子树内。设总叶子结点数为 t o t tot tot x x x f a fa fa 距离为 w w w,则这些叶子结点一共有 t o t − n u m [ x ] tot-num[x] totnum[x] 个,所以答案可以初始化为 w × ( t o t − n u m [ x ] ) w\times (tot-num[x]) w×(totnum[x])。在 f a fa fa 的子树外的点的答案即为 g [ f a ] g[fa] g[fa],在 f a fa fa 的子树内而不在 x x x 的子树内的答案即为 ∑ v ∈ s o n f a , v ≠ x ( f [ v ] + n u m [ v ] × w ) \sum_{v\in son_{fa},v \ne x} (f[v]+num[v]\times w) vsonfa,v=x(f[v]+num[v]×w),然后这道题就解决了(可能算的有点麻烦但是非常好想qwq

树上移动

一个人走,每条路径必须走两次,最后选一条路只走一次,那么必然是从 s s s 出发的最长路只走一次最优。

两个人走,也是先每条路走两次,然后每个机器人选一条路径走到底,那么必然这两条走到底的路合起来构成树的直径时最优。

所以dfs求最长路和次长路即可。

块的计数

当有多个最大值的时候,重复的贡献不好消去,所以考虑算出一共可以构成的连通块数和不包含最大值的连通块数,相减即为答案。

奇怪的错误出现了:维护两个值,分开写两个dfs就30分,合并成一个dfs就AC?我不李姐。

#include<bits/stdc++.h>
#define ll long long
#define ff(i,s,e) for(int i=s;i<=(e);++i)
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=1e5+5,mod=998244353;
int n,a[N],maxx=-2147483647;
int head[N],cnt;
struct qwq{
	int v,nxt;
}e[N<<1];
inline void add(int u,int v){
	e[++cnt]=qwq{v,head[u]};
	head[u]=cnt;
}
int sum[N],f[N];
inline void dfs(int x,int fa){
	if(a[x]!=maxx) f[x]=1;
	sum[x]=1;
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa) continue;
		dfs(v,x);
		f[x]=1ll*(f[v]+1)*f[x]%mod;
		sum[x]=1ll*(sum[v]+1)*sum[x]%mod;
	}
}
signed main(){
	n=read();
	ff(i,1,n) a[i]=read(),maxx=max(maxx,a[i]);
	ff(i,2,n){
		int u=read(),v=read();
		add(u,v),add(v,u);
	}
	dfs(1,0);
	int ans=0;
	ff(i,1,n) ans=(ans+sum[i]-f[i])%mod;
	printf("%d",(ans+mod)%mod);
	return 0; 
}

树的合并

古早题戳这里

权值统计

对于每个点x,统计经过x且路径上x的深度最浅的答案。x深度最浅有两组情况:x是端点或x不是端点。设f[x]为x是端点的情况的答案,则有 f [ x ] = ( ∑ v ∈ s o n x f [ v ] + 1 ) × a [ x ] f[x]=(\sum_{v\in son_x} f[v] +1)\times a[x] f[x]=(vsonxf[v]+1)×a[x],其中加一是因为要算上只有x自己这种情况。

考虑x不是端点的情况,则路径长这样:/\,权值为左边权值 × \times × 右边权值 × a [ i ] \times a[i] ×a[i],可以表示为 ( ( ∑ v ∈ s o n x f [ v ] ) 2 − ((\sum_{v\in son_x} f[v] )^2- ((vsonxf[v])2 ∑ v ∈ s o n x ( f [ v ] ) 2 ) ÷ 2 \sum_{v\in son_x} (f[v])^2)\div 2 vsonx(f[v])2)÷2

#include<bits/stdc++.h>
#define ll long long
#define int long long
#define ff(i,s,e) for(int i=s;i<=(e);++i)
using namespace std;
inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(ch>'9'||ch<'0'){if(ch=='-') f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
	return x*f;
}
const int N=1e5+5,mod=10086;
int n,a[N];
int head[N],cnt;
struct qwq{
	int v,nxt;
}e[N<<1];
inline void add(int u,int v){
	e[++cnt]=qwq{v,head[u]},head[u]=cnt;
}
int f[N];
ll ans;
inline void dfs(int x,int fa){
	int tot=0,sum=0;
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa) continue;
		dfs(v,x);
		sum+=f[v],tot+=f[v]*f[v];
	}
	f[x]=(sum+1)*a[x]%mod;
	ans=(ans+((sum*sum-tot)>>1)*a[x]+f[x])%mod;
}
signed main(){
	n=read();
	ff(i,1,n) a[i]=read();
	ff(i,2,n){
		int u=read(),v=read();
		add(u,v),add(v,u);
	}
	dfs(1,0);
	printf("%lld",ans);
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值