Hard
题目描述
存在一个具有 n 个节点的无向无根树,节点编号从 0 到 n - 1。给你整数 n 和一个长度为 n - 1 的二维整数数组 edges,其中 edges[i] = [ai, bi] 表示树中节点 ai 和 bi 之间存在一条边。
每个节点都有一个关联的价格。给你一个整数数组 price,其中 price[i] 是第 i 个节点的价格。
给定路径的价格总和是该路径上所有节点的价格之和。
另外,给你一个二维整数数组 trips,其中 trips[i] = [starti, endi] 表示第 i 次旅行从节点 starti 开始,通过任意路径前往节点 endi。
在执行第一次旅行之前,你可以选择一些不相邻的节点并将价格减半。
返回执行所有给定旅行的最小总价格。
示例 1:
输入:n = 4, edges = [[0,1],[1,2],[1,3]], price = [2,2,10,6], trips = [[0,3],[2,1],[2,3]]
输出:23
解释:上图表示以节点 2 为根的树。第一部分显示初始树,第二部分显示选择节点 0、2 和 3 并将其价格减半后的树。
对于第 1 次旅行,我们选择路径 [0,1,3]。该路径的价格总和为 1 + 2 + 3 = 6。
对于第 2 次旅行,我们选择路径 [2,1]。该路径的价格总和为 5 + 2 = 7。
对于第 3 次旅行,我们选择路径 [2,1,3]。该路径的价格总和为 5 + 2 + 3 = 10。
所有旅行的总价格为 6 + 7 + 10 = 23。
示例 2:
输入:n = 2, edges = [[0,1]], price = [2,2], trips = [[0,0]]
输出:1
解释:对于第 1 次旅行,我们选择路径 [0]。该路径的价格总和为 1。
约束:
- 1 <= n <= 50
- edges.length == n - 1
- 0 <= ai, bi <= n - 1
- edges 表示一个有效的树
- price.length == n
- price[i] 是偶数
- 1 <= price[i] <= 1000
- 1 <= trips.length <= 100
- 0 <= starti, endi <= n - 1
解题思路
这个问题需要分两步解决:
第一步:计算每个节点的访问频次 对于每次旅行,我们需要找到从起点到终点的路径,并统计每个节点在所有旅行中被访问的次数。由于是树结构,任意两点间只有唯一路径。我们可以使用DFS找到路径,并记录每个节点的访问频次。
第二步:树形动态规划选择减半节点 现在我们知道了每个节点的访问频次,问题转化为:在不相邻的节点中选择一些节点减半,使得总成本最小。这是一个经典的树形DP问题。
定义状态:
dp[v][0]:以v为根的子树中,v节点不减半时的最小成本dp[v][1]:以v为根的子树中,v节点减半时的最小成本
状态转移:
dp[v][0] = price[v] * freq[v] + sum(min(dp[child][0], dp[child][1]))(v不减半,子节点可减半可不减半)dp[v][1] = price[v]/2 * freq[v] + sum(dp[child][0])(v减半,所有子节点都不能减半)
最终答案是 min(dp[root][0], dp[root][1]),其中root可以是任意节点。
时间复杂度主要由寻找路径和树形DP两部分组成,都是O(n)级别的操作。
代码实现
class Solution {
public:
vector<vector<int>> graph;
vector<int> freq;
vector<int> price;
bool findPath(int curr, int target, int parent, vector<int>& path) {
path.push_back(curr);
if (curr == target) return true;
for (int next : graph[curr]) {
if (next != parent && findPath(next, target, curr, path)) {
return true;
}
}
path.pop_back();
return false;
}
pair<int, int> dfs(int node, int parent) {
int notHalve = price[node] * freq[node];
int halve = price[node] / 2 * freq[node];
for (int child : graph[node]) {
if (child == parent) continue;
auto [childNotHalve, childHalve] = dfs(child, node);
notHalve += min(childNotHalve, childHalve);
halve += childNotHalve;
}
return {notHalve, halve};
}
int minimumTotalPrice(int n, vector<vector<int>>& edges, vector<int>& price, vector<vector<int>>& trips) {
this->price = price;
graph.resize(n);
freq.resize(n, 0);
for (auto& edge : edges) {
graph[edge[0]].push_back(edge[1]);
graph[edge[1]].push_back(edge[0]);
}
for (auto& trip : trips) {
vector<int> path;
findPath(trip[0], trip[1], -1, path);
for (int node : path) {
freq[node]++;
}
}
auto [notHalve, halve] = dfs(0, -1);
return min(notHalve, halve);
}
};
class Solution:
def minimumTotalPrice(self, n: int, edges: List[List[int]], price: List[int], trips: List[List[int]]) -> int:
graph = [[] for _ in range(n)]
for a, b in edges:
graph[a].append(b)
graph[b].append(a)
freq = [0] * n
def find_path(curr, target, parent, path):
path.append(curr)
if curr == target:
return True
for next_node in graph[curr]:
if next_node != parent and find_path(next_node, target, curr, path):
return True
path.pop()
return False
for start, end in trips:
path = []
find_path(start, end, -1, path)
for node in path:
freq[node] += 1
def dfs(node, parent):
not_halve = price[node] * freq[node]
halve = price[node] // 2 * freq[node]
for child in graph[node]:
if child == parent:
continue
child_not_halve, child_halve = dfs(child, node)
not_halve += min(child_not_halve, child_halve)
halve += child_not_halve
return not_halve, halve
not_halve, halve = dfs(0, -1)
return min(not_halve, halve)
public class Solution {
private List<int>[] graph;
private int[] freq;
private int[] price;
private bool FindPath(int curr, int target, int parent, List<int> path) {
path.Add(curr);
if (curr == target) return true;
foreach (int next in graph[curr]) {
if (next != parent && FindPath(next, target, curr, path)) {
return true;
}
}
path.RemoveAt(path.Count - 1);
return false;
}
private (int, int) Dfs(int node, int parent) {
int notHalve = price[node] * freq[node];
int halve = price[node] / 2 * freq[node];
foreach (int child in graph[node]) {
if (child == parent) continue;
var (childNotHalve, childHalve) = Dfs(child, node);
notHalve += Math.Min(childNotHalve, childHalve);
halve += childNotHalve;
}
return (notHalve, halve);
}
public int MinimumTotalPrice(int n, int[][] edges, int[] price, int[][] trips) {
this.price = price;
graph = new List<int>[n];
freq = new int[n];
for (int i = 0; i < n; i++) {
graph[i] = new List<int>();
}
foreach (var edge in edges) {
graph[edge[0]].Add(edge[1]);
graph[edge[1]].Add(edge[0]);
}
foreach (var trip in trips) {
var path = new List<int>();
FindPath(trip[0], trip[1], -1, path);
foreach (int node in path) {
freq[node]++;
}
}
var (notHalve, halve) = Dfs(0, -1);
return Math.Min(notHalve, halve);
}
}
var minimumTotalPrice = function(n, edges, price, trips) {
// Build adjacency list
const graph = Array(n).fill(null).map(() => []);
for (const [a, b] of edges) {
graph[a].push(b);
graph[b].push(a);
}
// Count how many times each node is visited
const count = Array(n).fill(0);
// Find path between two nodes using DFS
const findPath = (start, end, visited = new Set()) => {
if (start === end) return [start];
visited.add(start);
for (const neighbor of graph[start]) {
if (!visited.has(neighbor)) {
const path = findPath(neighbor, end, visited);
if (path) {
return [start, ...path];
}
}
}
return null;
};
// Count visits for each trip
for (const [start, end] of trips) {
const path = findPath(start, end);
for (const node of path) {
count[node]++;
}
}
// Tree DP to find minimum cost
const dfs = (node, parent) => {
let noHalve = price[node] * count[node];
let halve = (price[node] / 2) * count[node];
for (const child of graph[node]) {
if (child !== parent) {
const [childNoHalve, childHalve] = dfs(child, node);
noHalve += Math.min(childNoHalve, childHalve);
halve += childNoHalve;
}
}
return [noHalve, halve];
};
const [noHalve, halve] = dfs(0, -1);
return Math.min(noHalve, halve);
};
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(n × trips.length + n) = O(n × trips.length),其中寻找每次旅行路径需要O(n),共有trips.length次;树形DP需要O(n) |
| 空间复杂度 | O(n),用于存储图的邻接表、频次数组和递归栈空间 |