最大独立集:需要从图中选择尽量多的点,使得这些点互不相邻。
其变形:最大化点权之和。
这道题是一般树的最大独立集
(附:二叉树的最大独立集)
另外,树和子树的关系类似原问题和子问题的关系,天然具有递归的特点,这是树形DP的出发点。
思考方向:1)选or不选;2)枚举选哪个
另外,对于数组递归的逻辑是从左到右,对于树递归的逻辑是从叶子到根节点,先解决左右子树。
这道题的思路是暴力dfs每条路径。
对于固定的trips
,每个节点经过的次数cnt
是不变的,于是首先计算图上所有节点经过的次数cnt
使用dfs计算cnt
。
然后,我们随便选一个节点出发 DFS,分类讨论:
对于一个节点x
和其孩子y
,存在两种情况:
1)x
的价格减一半,则其孩子y
的价格都不能变
2)x
的价格不变,则其孩子y
的价格可以减一半或者不变,选择其中最小值
于是节点x可以返回的值有两种情况:
1)价格减半时子树x
的最小值总和
2)价格不变时子树x
的最小值总和
取其中的最小值返回即可。
class Solution {
public:
int minimumTotalPrice(int n, vector<vector<int>>& edges, vector<int>& price, vector<vector<int>>& trips) {
vector<vector<int>> g(n);
for (auto e: edges) {
// g[e[0]] = e[1];
// g[e[1]] = e[0];
int x = e[0], y = e[1];
g[x].push_back(y);
g[y].push_back(x);
}
vector<int> cnt(n);
for (auto &t: trips) {
int end = t[1];
function<bool(int, int)> dfs = [&](int x, int fa) -> bool {
if (x == end) {
++cnt[x];
return true;
}
for (int y: g[x]) {
if (y != fa && dfs(y, x)) {
++cnt[x]; // x 是 end 的祖先节点,要到达 end 一定会经过 x
return true;
}
}
return false;
};
dfs(t[0], -1);
}
function<pair<int, int>(int, int)> dfs = [&] (int x, int fa) -> pair<int, int> {
int non_halve = price[x] * cnt[x];
int halve = non_halve / 2;
for (auto y: g[x]) {
if (y != fa) {
auto [nh, h] = dfs(y, x);
non_halve += min(nh, h);
halve += nh;
}
}
return {non_halve, halve};
};
auto [nh, h] = dfs(0, -1);
return min(nh, h);
}
};
其中,
y != fa
的判断是用来避免回头访问父节点,从而避免出现环路,确保在遍历树或图时不会重复访问同一个节点,同时也避免了在遍历过程中出现死循环。
另外注意这里C++的匿名函数的使用。