树形DP
普通树形DP
由于树固有的递归性质,树形 DP 一般都是递归进行的。
——通常用dp[i]表示为以节点i为根的子树规划得到的答案;
例题
https://www.luogu.com.cn/problem/P1352
本题dp[i][f]代表根为i的子树的点的状态0/1,0是不在,1是存在
Code
点击查看代码
const int maxn = 6e3 + 10;
int n, r[maxn];
int dp[maxn][2];
vector<int> ve[maxn];
int in[maxn];
int dfs(int u, int f) {
if (dp[u][f]) { return dp[u][f]; }
if (f) {
int sum = 0;
for (auto v : ve[u]) {
sum += dfs(v, 0);
}
dp[u][f] = max(dp[u][f], sum + r[u]);
}
else {
int sum = 0;
for (auto v : ve[u]) {
sum += max(dfs(v, 0), dfs(v, 1));
}
dp[u][f] = max(dp[u][f], sum);
}
return dp[u][f];
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> r[i];
}
int root;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
ve[v].push_back(u);
in[u]++;
}
for (int i = 1; i <= n; i++) {
if (!in[i]) {
root = i;
break;
}
}
cout << max(dfs(root, 1), dfs(root, 0));
}
树上背包
背包问题与树形问题的结合,即在树上进行背包dp
方法与之类似
重点
在背包时要用tmp来替换dp,防止在转移时错误调用。
https://www.luogu.com.cn/problem/P2014
dp[i][j]表示根为i的子树的背包容量为j时最多装下的价值
状态转移:
for (int j = m + 1; j >= 1; j--) {
代表u的背包容量,j>=1是因为u必须装着,要占用一个空间
for (int k = 0; k < j; k++) {
代表u的儿子v的背包容量,k<j是因为儿子不能占用全部空间
dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[v][k]);
}
}
Code
点击查看代码
const int maxn = 1e5 + 10;
int n, m, s[maxn];
vector<int> ve[310];
int dp[310][310];
void dfs(int u) {
dp[u][1] = s[u];
for (auto v : ve[u]) {
dfs(v);
for (int j = m + 1; j >= 1; j--) {
for (int k = 0; k < j; k++) {
dp[u][j] = max(dp[u][j], dp[u][j - k] + dp[v][k]);
}
}
}
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
int k;
cin >> k >> s[i];
ve[k].push_back(i);
}
dfs(0);
cout << dp[0][m + 1];
}
换根DP
树形 DP 中的换根 DP 问题又被称为二次扫描,通常不会指定根结点,并且根结点的变化会对一些值,例如子结点深度和、点权和等产生影响;
重点
常用两遍dfs,第一次强令一个根预处理,第二次从第一个根往下替换,动态规划;
第二次dfs要时刻注意根与儿子的关系;
以及在换根时节点信息的变化,该更新的要更新;
防止用错误的数据得到错误的答案
例题
https://www.luogu.com.cn/problem/P3478
从根u换成儿子v变根,以v为子树的点深度-1,其他点+1;
Code
点击查看代码
const int maxn = 1e6 + 10;
int read();
int n;
vector<int> ve[maxn];
int sz[maxn];
int ans[maxn];
void dfs(int u, int fa) {
sz[u] = 1;
for (auto v : ve[u]) {
if (v == fa) continue;
dfs(v, u);
sz[u] += sz[v];
}
ans[1] += sz[u];
}
void dfs1(int u, int fa) {
for (auto v : ve[u]) {
if (v == fa) continue;
ans[v] = ans[u] + n - sz[v] * 2;
dfs1(v, u);
}
}
void solve() {
cin >> n;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
ve[u].pb(v);
ve[v].pb(u);
}
dfs(1, 0);
dfs1(1, 0);
int res = 0, mark = 0;
for (int i = 1; i <= n; i++) {
if (ans[i] > res) {
res = ans[i];
mark = i;
}
}
cout << mark;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人