问题描述
2646. 最小化旅行的价格总和 (Hard)
现有一棵无向、无根的树,树中有 n
个节点,按从 0
到 n - 1
编号。给你一个整数 n
和一个长度为 n - 1
的二维整数数
组 edges
,其中 edges[i] = [aᵢ, bᵢ]
表示树中节点 aᵢ
和
bᵢ
之间存在一条边。
每个节点都关联一个价格。给你一个整数数组 price
,其中 pri ce[i]
是第 i
个节点的价格。
给定路径的 价格总和 是该路径上所有节点的价格之和。
另给你一个二维整数数组 trips
,其中 trips[i] = [startᵢ, e ndᵢ]
表示您从节点 startᵢ
开始第 i
次旅行,并通过任何你
喜欢的路径前往节点 endᵢ
。
在执行第一次旅行之前,你可以选择一些 非相邻节点 并将价格 减半。
返回执行所有旅行的最小价格总和。
示例 1:
![](https://assets.leetcode.com/uploads/2023/03/16/diagram2. png)
输入: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] 。路径的价格总和为 2 + 5 = 7 。
第 3 次旅行,选择路径 [2,1,3] 。路径的价格总和为 5 + 2 + 3 =
10 。
所有旅行的价格总和为 6 + 7 + 10 = 23 。可以证明,23 是可以实
现的最小答案。
示例 2:
![](https://assets.leetcode.com/uploads/2023/03/16/diagram3. png)
输入:n = 2, edges = [[0,1]], price = [2,2], trips = [[0,0]]
输出:1
解释:
上图表示将节点 0 视为根之后的树结构。第一个图表示初始树,第
二个图表示选择节点 0 并使其价格减半后的树。
第 1 次旅行,选择路径 [0] 。路径的价格总和为 1 。
所有旅行的价格总和为 1 。可以证明,1 是可以实现的最小答案。
提示:
1 <= n <= 50
edges.length == n - 1
0 <= aᵢ, bᵢ <= n - 1
edges
表示一棵有效的树price.length == n
price[i]
是一个偶数1 <= price[i] <= 1000
1 <= trips.length <= 100
0 <= startᵢ, endᵢ <= n - 1
解题思路
贡献法 + 树上 dp
首先,对每条路径,在树上所有结点的价格已经确定的情况下,由于树是不存在环的,那么从起点到终点的非重复路径是唯一的,而这一最短路径必定对应着最小的路径价格。
因此,我们需要遍历 trip
,并对树上每个结点,标记它一共被经过了多少次。
然后便是树上 dp 的思想了,即枚举当前结点选或不选两种情况,划分为更简单的子问题,从而可以递归解决,这里的返回值最好是 pair<int, int>
,一个表示当前结点价格减半的最小价格之和,一个表示不减半的价格之和。
对于树的遍历,由于不存在环,我们不需要使用 vis
数组进行标记,只需要在 dfs
函数中添加一个参数表示其父结点就可以了。
代码
class Solution {
public:
// 寻找不重复路径
void dfs(int cur, int end, vector<int> &path, vector<vector<int>> &graph, vector<bool> &vis, vector<vector<int>> &all_path) {
if (cur == end) {
all_path.push_back(path);
return;
}
for (auto next : graph[cur]) {
if (!vis[next]) {
path.push_back(next);
vis[next] = true;
dfs(next, end, path, graph, vis, all_path);
path.pop_back();
vis[next] = false;
}
}
}
pair<int, int> GetRes(vector<vector<int>> &graph, vector<int> ×, int fa, int idx, vector<int> &price) {
// 如何避免重复?
int half = price[idx] * times[idx] / 2, not_half = price[idx] * times[idx];
for (auto next : graph[idx]) {
if (next == fa) {
continue;
}
auto [sum_half, sum_not_half] = GetRes(graph, times, idx, next, price);
half += sum_not_half; // next 不能减半了
not_half += min(sum_half, sum_not_half);
}
return {half, not_half};
}
int minimumTotalPrice(int n, vector<vector<int>> &edges, vector<int> &price, vector<vector<int>> &trips) {
// 统计每个结点的贡献,即每个结点会被经过多少次
vector<int> times(n);
vector<vector<int>> graph(n);
for (int i = 0; i < edges.size(); ++i) {
int x = edges[i][0], y = edges[i][1];
graph[x].push_back(y);
graph[y].push_back(x);
}
vector<vector<int>> all_path;
for (auto &vec : trips) {
int start = vec[0], end = vec[1];
vector<int> path;
vector<bool> vis(n);
vis[start] = true;
path.push_back(start);
dfs(start, end, path, graph, vis, all_path);
}
vector<int> cnt(n);
for (const auto &path_ : all_path) {
for (int idx : path_) {
++times[idx];
}
}
auto [half, not_half] = GetRes(graph, times, 0, 0, price);
return min(half, not_half);
}
};
标签:int,graph,price,Hard,2646,vector,half,最小化,path
From: https://www.cnblogs.com/zwyyy456/p/17477710.html