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;
}
相关推荐
电子_咸鱼8 小时前
常见面试题——滑动窗口算法
c++·后端·python·算法·leetcode·哈希算法·推荐算法
mit6.8248 小时前
hash+presum判等|幻方0
算法
萌>__<新8 小时前
力扣打卡每日一题————最小覆盖子串
数据结构·算法·leetcode·滑动窗口·哈希表
ada7_9 小时前
LeetCode(python)230.二叉搜索树中第k小的元素
python·算法·leetcode·链表
TL滕9 小时前
从0开始学算法——第十五天(滑动窗口练习)
笔记·学习·算法
DuHz9 小时前
milliLoc 论文精读:把商用毫米波 FMCW 的绝对测距从“厘米栅格”推进到“毫米级连续值”,并顺带修正 AoA 的系统相位偏差
论文阅读·物联网·算法·信息与通信·毫米波雷达
肥大毛9 小时前
C++入门学习---结构体
开发语言·c++·学习
qq_401700419 小时前
Linux文件锁解决多进程并发
linux·服务器·算法
长安er10 小时前
LeetCode 83/237/82 链表删除问题-盒子模型
数据结构·算法·leetcode·链表·力扣