树上背包问题

本文探讨了在解决树上背包问题时如何优化树形动态规划的效率,通过避免不必要的枚举和更新子树大小来降低复杂度。举例介绍了CF 1499F和2020ICPC南京站-M两道题目,详细阐述了问题转换和状态转移矩阵的构建,强调了在更新过程中的一些关键细节,如子树大小的实时计算和转移顺序的重要性。

我们会遇到一类问题:
给你一棵n<=5000的树,要在树上跑树形dp。我们可以轻松的想出dp的定义式,第一维是代表节点,第二维是题目要维护的信息。范围均是5000。
如果我们暴力去跑的话,对于每个节点都要枚举所有子节点,并且第二维要枚举当前节点的值和子节点的值。
这样效率是O(n3)O(n^3)O(n3)显然是不够的。
我们可以加入一个优化,考虑当前子树的size,子树如果比较小,第二维就没有必要枚举那么多。更进一步,我们不要预处理每个子树的size,在dp的时候不断更新,每次的size只有已经递归过的子树和根节点,这样子合并的效率是O(n2)O(n^2)O(n2)

例题1.CF 1499F
题意:给定一颗树,让你找出一个边集的数量,边集需满足:去掉集合中的边,剩下的所有子树的直径均不大于给定的k。
思路:
我们要保证不能出现有一个子树到根的最长路径len1和另一个子树到根的最长路径len2使得len1+len2>k。
为了判断这个情况我们就能构建一个dp定义式
dp[u][len]代表以x为根的子树,最长到达根u的路径长度为len的边集数量。
我们在转移的时候,有两种情况。
1.u-v这条边不连接,那么直接先前的答案*这个子树的答案即可
2.u-v这条边连接,为了保证最开始的条件,转移的时候要避免这样的答案合并。

一个小trick:每次先把当前的dp值先拎出来,转移完再放回去,这样不用考虑类似01背包那样变量循环的方向问题。

const int maxn=5e3+5;
const ll mod=998244353;
int head[maxn],tot;
struct nn{
   
   int v,nxt;}g[maxn<<1];
void add_edge(int u,int v){
   
   g[++tot]={
   
   v,head[u]};head[u]=tot;}
ll dp[maxn][maxn],sz[maxn],n,mx,now[maxn];
void dfs(int u,int fa){
   
   
    sz[u]=1;
    dp[u][0]=1;
    for (int i=head[u];i;i=g[i].nxt){
   
   
        int v=g[i].v;
        if (v==fa)continue;
        dfs(v,u);
        for (int j=0;j<=n;j++)now[j]=dp[u][j],dp[u][j]=0;
        for (int j=0;j<=sz[u];j++){
   
   
            for (int k=0;k<=min(sz[v],
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值