Leetcode 2646. Minimize the Total Price of the Trips
Woodyiiiiiii opened this issue · comments
2646. Minimize the Total Price of the Trips
好题。综合了很多**。
首先看清题意。题目要求的是在第一次操作前就要考虑是否对结点价值减半了,同时要求选择减半的结点不相邻。那么这里我陷入了思维误区,以为贪心求两种分类就够了(从0结点选择或不选择开始分成两类),其实很明显错误了,因为每个结点的价值不同,可能会出现选择减半的结点距离很远的情况。那这种就想到了337. House Robber III的**,也就是树形DP。
显然这种non-adjacent的题型都是用House Robber的DP思路。
又因为可以观察到对每个trip来说,路径都是固定的(因为是树),同时结点之间关联不大。所以接下来我们根据结点数量少的限制,可以对每个trip跑一遍DFS,记录路径经过的结点次数,最后汇总,DP计算总价值。
class Solution {
Map<Integer, Set<Integer>> graph;
int[] price;
int[] count;
List<Integer> routine;
public int minimumTotalPrice(int n, int[][] edges, int[] price, int[][] trips) {
this.graph = new HashMap<>();
for (int i = 0; i < n; i++) {
graph.put(i, new HashSet<>());
}
for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
graph.get(edge[1]).add(edge[0]);
}
this.price = price;
count = new int[n];
for (int[] trip : trips) {
routine = new ArrayList<>();
List<Integer> path = new ArrayList<>();
path.add(trip[0]);
findPath(-1, trip[0], trip[1], path);
for (int i : routine) {
count[i]++;
}
}
int[] res = dfs(0, -1);
return Math.min(res[0], res[1]);
}
private int[] dfs(int cur, int pre) {
int[] res = new int[2];
int notHalve = price[cur] * count[cur];
int halve = notHalve / 2;
for (int next : graph.get(cur)) {
if (next == pre) {
continue;
}
int[] nextRes = dfs(next, cur);
halve += nextRes[0];
notHalve += Math.min(nextRes[0], nextRes[1]);
}
res[0] = notHalve;
res[1] = halve;
return res;
}
private void findPath(int pre, int cur, int end, List<Integer> path) {
if (cur == end) {
routine = new ArrayList<>(path);
return;
}
for (int next : graph.get(cur)) {
if (next == pre) {
continue;
}
path.add(next);
findPath(cur, next, end, path);
path.remove(path.size() - 1);
}
}
}
时间复杂度O(nm),其中m是trip长度;空间复杂度O(n)。
第二种方法是用Tarjan 离线 LCA + 树上差分,太难了看详解。
相关联的题目:
- 2646. Minimize the Total Price of the Trips (本题)
- 337. House Robber III (树形DP)
- 2467. Most Profitable Path in a Tree (路径结点次数+1)
- 1373. Maximum Sum BST in Binary Tree (树形DP)
337. House Robber III
下面是我之前的做法,深度后序遍历,其中创建Map缓存防止重复子问题出现。
class Solution {
Map<TreeNode, Integer> memo = new HashMap<>();
public int rob(TreeNode root) {
if (root == null) {
return 0;
}
if (memo.containsKey(root)) {
return memo.get(root);
}
int l = rob(root.left);
int r = rob(root.right);
int ll = root.left == null ? 0 : rob(root.left.left);
int lr = root.left == null ? 0 : rob(root.left.right);
int rr = root.right == null ? 0 : rob(root.right.right);
int rl = root.right == null ? 0 : rob(root.right.left);
int max = Math.max(l + r, root.val + ll + lr + rr + rl);
memo.put(root, max);
return max;
}
}
其中发现,每个结点最后的值只跟他的孩子结点有关,所以我们可以设计后序树形DP,这样省去了缓存。
class Solution {
public int rob(TreeNode root) {
int[] res = dfs(root);
return Math.max(res[0], res[1]);
}
private int[] dfs(TreeNode root) {
int[] res = new int[2];
if (root == null) {
return res;
}
res[0] = root.val;
int[] left = dfs(root.left);
int[] right = dfs(root.right);
res[1] += Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
res[0] += (left[1] + right[1]);
return res;
}
}