C++树形DP(树上分组背包)

树形DP 代码框架见下 对应小明的背包6 蓝桥云课

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

#define type int
#define maxn 1010
#define maxv 110

struct Item {
    int vol;
    int wei;
}items[maxn];

//child[i][j]代表i个第j个儿子是child[i][j]
vector<int> child[maxn];
//dp[i][j]代表以i为根的子树中,选择容量总和为j的物品,得到的最优价值
type dp[maxn][maxv];
//sumVol[i] 代表以i为根的子树中,所有子节点的总容量
int sumVol[maxn];
//n件物品,最大容量为V
int n, V;
// 状态初始值
type KnapsackTree_InitValue(int u) {
    return 0;
}
//非法状态值
type KnapsackTree_InfValue() {
    return -1000000000;
}
//状态转移的最优函数,有可能是最大值,有可能是最小值,有可能是方案数
type KnapsackTree_Opt(type curVal, type preVal, type itemVal) {
    return max(curVal, preVal + itemVal);
}
//这一步非常关键,目的是把滚动数组dpu中计算的数据,转移到dp数组中
//dpu是临时数据,递归结束就销毁了
//dp是持久化数据(全局数据),递归结束一直保存
void KnapsackTree_Post(int u, type dpu[maxv]) {
    for (int j = 1; j <= V; ++j) {
        // 当前这个物品不选
        dp[u][j] = (j == 0) ? KnapsackTree_InitValue(u) : KnapsackTree_InfValue();
        // 当前物品选择的情况
        if (j >= items[u].vol) {
            dp[u][j] = KnapsackTree_Opt(
                dp[u][j],
                dpu[j - items[u].vol], items[u].wei
            );
        }
    }
}
// 树上分组初始化
//模版代码 基本不用改
int KnapsackTree_Init(type dpu[2][maxv], int u) {
    int pre = 0;
    dpu[pre][0] = KnapsackTree_InitValue(u);
    for (int i = 1; i <= V; ++i) {
        dpu[pre][i] = KnapsackTree_InfValue();
    }
    return pre;
}

type KnapsackTree_GetAnswer(int root) {
    type ans = KnapsackTree_InfValue();
    for (int i = 0; i <= V; ++i) {
        ans = KnapsackTree_Opt(ans, dp[root][i], 0);
    }
    return ans;
}
//模版代码,基本不用改
void KnapsackTree_DFS(int u, int fat) {
    type dpu[2][maxv];
    int pre = KnapsackTree_Init(dpu, u);
    int cur = 1 - pre;
    sumVol[u] = items[u].vol;
    for (int i = 0; i < child[u].size(); ++i) {
        int v = child[u][i];
        if (v == fat) continue;
        KnapsackTree_DFS(v, u);
        sumVol[u] += sumVol[v];
        for (int j = 0; j <= V; ++j) {
            dpu[cur][j] = KnapsackTree_InfValue();
            for (int k = 0; k <= j && k <= sumVol[v]; ++k) {
                type tmp = dpu[pre][j - k] + dp[v][k];
                dpu[cur][j] = KnapsackTree_Opt(dpu[cur][j], dpu[pre][j - k], dp[v][k]);
            }
        }
        swap(pre, cur);
    }
    KnapsackTree_Post(u, dpu[pre]);
}
int main()
{
    cin >> n >> V;
    for (int i = 1; i <= n; ++i) {
        int x;
        cin >> items[i].vol >> items[i].wei >> x;
        child[x].push_back(i);
    }
    KnapsackTree_DFS(0, -1);
    cout << KnapsackTree_GetAnswer(0) << endl;

    // 请在此输入您的代码
    return 0;
}

代码 1 对应蓝桥云课 包含1的树上联通计数块2

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

#define type long long 
#define maxn 2010
#define maxv 2010
#define mod 998244353

struct Item {
    int vol;
    int wei;
}items[maxn];

//child[i][j]代表i个第j个儿子是child[i][j]
vector<int> child[maxn];
//dp[i][j]代表以i为根的子树中,选择容量总和为j的物品,得到的最优价值
type dp[maxn][maxv];
//sumVol[i] 代表以i为根的子树中,所有子节点的总容量
int sumVol[maxn];
//n件物品,最大容量为V
int n, V;
// 状态初始值
type KnapsackTree_InitValue(int u) {
    return 1;
}
//非法状态值
type KnapsackTree_InfValue() {
    return 0;
}
//状态转移的最优函数,有可能是最大值,有可能是最小值,有可能是方案数
type KnapsackTree_Opt(type curVal, type preVal, type itemWei) {
    return (curVal + preVal * itemWei) % mod;
}
//这一步非常关键,目的是把滚动数组dpu中计算的数据,转移到dp数组中
//dpu是临时数据,递归结束就销毁了
//dp是持久化数据(全局数据),递归结束一直保存
void KnapsackTree_Post(int u, type dpu[maxv]) {
    for (int j = 0; j <= V; ++j) {
        // 当前这个物品不选
        dp[u][j] = (j == 0) ? KnapsackTree_InitValue(u) : KnapsackTree_InfValue();
        // 当前物品选择的情况
        if (j >= items[u].vol) {
            dp[u][j] = KnapsackTree_Opt(
                dp[u][j],
                dpu[j - items[u].vol], items[u].wei
            );
        }
    }
}
// 树上分组初始化
//模版代码 基本不用改
int KnapsackTree_Init(type dpu[2][maxv], int u) {
    int pre = 0;
    dpu[pre][0] = KnapsackTree_InitValue(u);
    for (int i = 1; i <= V; ++i) {
        dpu[pre][i] = KnapsackTree_InfValue();
    }
    return pre;
}
// 根据题目意思做修改
type KnapsackTree_GetAnswer(int root) {

    return dp[root][V];
}
//模版代码,基本不用改
void KnapsackTree_DFS(int u, int fat) {
    type dpu[2][maxv];
    int pre = KnapsackTree_Init(dpu, u);
    int cur = 1 - pre;
    sumVol[u] = items[u].vol;
    for (int i = 0; i < child[u].size(); ++i) {
        int v = child[u][i];
        if (v == fat) continue;
        KnapsackTree_DFS(v, u);
        sumVol[u] += sumVol[v];
        for (int j = 0; j <= V; ++j) {
            dpu[cur][j] = KnapsackTree_InfValue();
            for (int k = 0; k <= j && k <= sumVol[v]; ++k) {
                dpu[cur][j] = KnapsackTree_Opt(dpu[cur][j], dpu[pre][j - k], dp[v][k]);
            }
        }
        swap(pre, cur);
    }
    KnapsackTree_Post(u, dpu[pre]);
}
int main()
{
    cin >> n >> V;
    for (int i = 1; i <= n; ++i) {
        items[i].vol = 1;
        items[i].wei = 1;
    }
    for (int i = 0; i < n - 1; ++i) {
        int x, y;
        cin >> x >> y;
        child[x].push_back(y);
        child[y].push_back(x);
    }
    KnapsackTree_DFS(1, -1);
    cout << KnapsackTree_GetAnswer(1) << endl;

    // 请在此输入您的代码
    return 0;
}

代码练习 2 最小子树 代码见下,对应蓝桥云课

cpp 复制代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

#define type int
#define maxn 100010
#define maxv 3

struct Item {
    int vol;
    int wei;
}items[maxn];

//child[i][j]代表i个第j个儿子是child[i][j]
vector<int> child[maxn];
//dp[i][j]代表以i为根的子树中,选择容量总和为j的物品,得到的最优价值
type dp[maxn][maxv];
//sumVol[i] 代表以i为根的子树中,所有子节点的总容量
int sumVol[maxn];
//n件物品,最大容量为V
int n, V;
// 状态初始值
type KnapsackTree_InitValue(int u) {
    return items[u].wei;
}
//非法状态值
type KnapsackTree_InfValue() {
    return 1000000000;
}
//
//如果是加边,直接返回 KnapsackTree_InfValue
//如果是删边,需要根据v值来判定
type KnapsackTree_CurInitValue(type dpu[maxv], int j){
  return (j > 0) ? dpu[j-1] : KnapsackTree_InfValue();
}
//状态转移的最优函数,有可能是最大值,有可能是最小值,有可能是方案数
type KnapsackTree_Opt(type curVal, type preVal, type itemVal) {
    return min(curVal, preVal + itemVal);
}
//这一步非常关键,目的是把滚动数组dpu中计算的数据,转移到dp数组中
//dpu是临时数据,递归结束就销毁了
//dp是持久化数据(全局数据),递归结束一直保存
void KnapsackTree_Post(int u, type dpu[maxv]) {
  for(int j=0; j <= V; ++j){
    dp[u][j] = dpu[j];
  }
}
// 树上分组初始化
//模版代码 基本不用改
int KnapsackTree_Init(type dpu[2][maxv], int u) {
    int pre = 0;
    dpu[pre][0] = KnapsackTree_InitValue(u);
    for (int i = 1; i <= V; ++i) {
        dpu[pre][i] = KnapsackTree_InfValue();
    }
    return pre;
}

type KnapsackTree_GetAnswer(int root) {
    type ans = KnapsackTree_InfValue();
    for(int i=1; i <= n; ++i){
      int mv = (i == root) ? V:V-1;
      for(int j=0; j <= mv; ++j){
        ans = KnapsackTree_Opt(ans, dp[i][j], 0);
      }
    }
    return ans;
}
//模版代码,基本不用改
void KnapsackTree_DFS(int u, int fat) {
    type dpu[2][maxv];
    int pre = KnapsackTree_Init(dpu, u);
    int cur = 1 - pre;
    sumVol[u] = items[u].vol;
    for (int i = 0; i < child[u].size(); ++i) {
        int v = child[u][i];
        if (v == fat) continue;
        KnapsackTree_DFS(v, u);
        sumVol[u] += sumVol[v];
        for (int j = 0; j <= V; ++j) {
            dpu[cur][j] = KnapsackTree_CurInitValue(dpu[pre], j);
            for (int k = 0; k <= j && k <= sumVol[v]; ++k) {
                dpu[cur][j] = KnapsackTree_Opt(dpu[cur][j], dpu[pre][j - k], dp[v][k]);
            }
        }
        swap(pre, cur);
    }
    KnapsackTree_Post(u, dpu[pre]);
}
int main()
{
    cin >> n;
    V = 2;
    for (int i = 1; i <= n; ++i) {
        items[i].vol = 1;
        cin >> items[i].wei;
    }
    for(int i=0; i < n-1; ++i){
      int x, y;
      cin >> x >> y;
      child[x].push_back(y);
      child[y].push_back(x);
    }
    KnapsackTree_DFS(1, -1);
    cout << KnapsackTree_GetAnswer(1) << endl;

    // 请在此输入您的代码
    return 0;
}

代码练习 3 对应蓝桥云课 取气球 代码见下

cpp 复制代码
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

#define type int
#define maxn 5010
#define maxv 5010

struct Item {
    int vol;
    int wei;
    int edge;
}items[maxn];

//child[i][j]代表i个第j个儿子是child[i][j]
vector<int> child[maxn];
//dp[i][j]代表以i为根的子树中,选择容量总和为j的物品,得到的最优价值
type dp[maxn][maxv];
//sumVol[i] 代表以i为根的子树中,所有子节点的总容量
int sumVol[maxn];
//n件物品,最大容量为V
int n, V;
// 状态初始值
type KnapsackTree_InitValue(int u) {
    return 0;
}
//非法状态值
type KnapsackTree_InfValue() {
    return 1000000000;
}
//
//如果是加边,直接返回 KnapsackTree_InfValue
//如果是删边,需要根据v值来判定
type KnapsackTree_CurInitValue(type dpu[maxv], int j) {
    return KnapsackTree_InfValue();
}
//状态转移的最优函数,有可能是最大值,有可能是最小值,有可能是方案数
type KnapsackTree_Opt(type curVal, type preVal, type itemVal) {
    return min(curVal, preVal + itemVal);
}
//这一步非常关键,目的是把滚动数组dpu中计算的数据,转移到dp数组中
//dpu是临时数据,递归结束就销毁了
//dp是持久化数据(全局数据),递归结束一直保存
void KnapsackTree_Post(int u, type dpu[maxv]) {
    for (int j = 0; j <= V; ++j) {
        dp[u][j] = dpu[j];
    }
    // 割掉绳子
    dp[u][sumVol[u]] = KnapsackTree_Opt(dp[u][sumVol[u]], items[u].edge, 0);
    // 戳破气球
    dp[u][sumVol[u] - 1] = KnapsackTree_Opt(dp[u][sumVol[u] - 1], items[u].wei, 0);
}
// 树上分组初始化
//模版代码 基本不用改
int KnapsackTree_Init(type dpu[2][maxv], int u) {
    int pre = 0;
    dpu[pre][0] = KnapsackTree_InitValue(u);
    for (int i = 1; i <= V; ++i) {
        dpu[pre][i] = KnapsackTree_InfValue();
    }
    return pre;
}

type KnapsackTree_GetAnswer(int root, int w) {
    for (int i = n; i >= 0; --i) {
        if (dp[root][i] <= w) {
            return i;
        }
    }
    return 0;
}
//模版代码,基本不用改
void KnapsackTree_DFS(int u, int fat) {
    type dpu[2][maxv];
    int pre = KnapsackTree_Init(dpu, u);
    int cur = 1 - pre;
    sumVol[u] = items[u].vol;
    for (int i = 0; i < child[u].size(); ++i) {
        int v = child[u][i];
        if (v == fat) continue;
        KnapsackTree_DFS(v, u);
        sumVol[u] += sumVol[v];
        for (int j = 0; j <= V; ++j) {
            dpu[cur][j] = KnapsackTree_CurInitValue(dpu[pre], j);
            for (int k = 0; k <= j && k <= sumVol[v]; ++k) {
                dpu[cur][j] = KnapsackTree_Opt(dpu[cur][j], dpu[pre][j - k], dp[v][k]);
            }
        }
        swap(pre, cur);
    }
    KnapsackTree_Post(u, dpu[pre]);
}
int main()
{
    int w;
    cin >> n >> w;
    V = n;
    for (int i = 1; i <= n; ++i) {
        items[i].vol = 1;
        cin >> items[i].wei;
    }
    items[1].edge = KnapsackTree_InfValue();
    for (int i = 1; i <= n - 1; ++i) {
        int x, y;
        cin >> x >> y;
        child[x].push_back(i + 1);
        items[i + 1].edge = y;
    }
    KnapsackTree_DFS(1, -1);
    cout << KnapsackTree_GetAnswer(1, w) << endl;

    // 请在此输入您的代码
    return 0;
}
相关推荐
得物技术1 小时前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六4 小时前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术5 小时前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize5 小时前
初识DFS 与 BFS:递归、队列与图遍历
算法
罗西的思考19 小时前
机器人 / 强化学习】HIL-SERL:人类在环驱动的具身智能进化框架
人工智能·算法·机器学习
美团技术团队1 天前
LongCat 开源 VitaBench 2.0:长期动态智能体基准新标杆
人工智能·算法
用户805533698031 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
To_OC2 天前
LC 207 课程表:刚学图论那会儿,我连这是拓扑排序都没看出来
javascript·算法·leetcode
To_OC2 天前
LC 208 实现 Trie 前缀树:曾被名字劝退,写完发现是送分题
javascript·算法·leetcode
BadBadBad__AK2 天前
线段树维护区间 k 次方和
c++·数学·算法·stl