A.没有上司的舞会 基础树形DP
emmm,蒟蒻发现自己的DP太辣鸡了。。。所以来练练DP,这题的话实际上应该算是树DP的入门题吧,转移还是挺好想的。
每次在每个节点都会有个选择,就是选还是不选,如果选的话,那么它的儿子节点就不能选,如果不选的话它的儿子节点就可以选,也就是说我们需要另开一维状态来记录每个节点是否选自己的情况,那么就很容易得出如下方程:
dp[x][0]+=max(0,max(dp[v][1],dp[v][0]));//如果不选当前节点,那么儿子节点可以任意选
dp[x][1]+=max(0,dp[v][0]);//如果选择当前节点,那么只能选择儿子节点不存在的情况
AC代码
#include<bits/stdc++.h> using namespace std; vector<int>son[10010]; int f[10010][2], v[10010], happy[10010], n; void dfs(int x) { f[x][0] = 0, f[x][1] = happy[x]; for (int i = 0; i < son[x].size(); ++i) { int y = son[x][i]; dfs(y); f[x][0] += max(f[y][0], f[y][1]); f[x][1] += f[y][0]; } } int main() { //freopen("in.txt", "r", stdin); cin >> n; for (int i = 1; i <= n; ++i)cin >> happy[i];//快乐指数 for (int i = 1; i < n; ++i) { int x, y; cin >> x >> y; v[x] = 1; // x has a father son[y].push_back(x); } int root; for(int i = 1; i<= n;++i) if (!v[i]) { root = i; break; } dfs(root); cout << max(f[root][0], f[root][1]) << endl; }
算法竞赛进阶指南原文:
正如深度优先和广度优先都可以对树或图进行遍历一样,除了自顶向下的递归外。我们也可以使用自底向上的Topo排序来执行树形DP。但通常前者就足够了。
B.选课 背包类树形DP
思路:
每堂课和学它必修的课连一条边。为了方便,每个入度为0的课(即可以直接选的课)与一个虚拟的n+1节点连一条边,然后在树上跑01背包即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
vector<int> son[310];
int f[310][310], s[310], n, m;
void dp(int x) {
f[x][0] = 0;
for (int i = 0; i < son[x].size(); i++) { // 循环子节点(物品)
int y = son[x][i];
dp(y);
for (int t = m; t >= 0; t--) // 倒序循环当前选课总门数(当前背包体积)
for (int j = 0; j <= t; j++) // 循环更深子树上的选课门数(组内物品)
f[x][t] = max(f[x][t], f[x][t-j] + f[y][j]);
/* 或者
for (int j = t; j >= 0; j--)
if (t + j <= m)
f[x][t+j] = max(f[x][t+j], f[x][t] + f[y][j]);
这两种写法j分别用了正序和倒序循环
是为了正确处理组内体积为0的物品(本题正序倒序都可以AC是因为体积为0的物品价值恰好也为0)
请读者结合0/1背包问题中DP的“阶段”理论思考 */
}
if (x != 0) // x不为0时,选修x本身需要占用1门课,并获得相应学分
for(int t = m; t > 0; t--)
f[x][t] = f[x][t-1] + s[x];
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
int x;
cin >> x >> s[i];
son[x].push_back(i);
}
memset(f, 0xcf, sizeof(f)); // -INF
dp(0);
cout << f[0][m] << endl;
}