题解 | #中世纪的商路#
中世纪的商路
https://www.nowcoder.com/practice/62fcb41a02aa42798c856c17bef25776
题目链接
题目描述
给定 N 个贸易机会,每个机会有出发时间、到达时间、所需商队规模和预期利润。在任何时刻,只能派遣一支商队,因此不能同时进行时间重叠的贸易活动(一个在 t 时刻结束的活动与一个在 t 时刻开始的活动不算重叠)。
目标是在不超过最大商队规模 C 的前提下,选择一个不重叠的贸易活动子集,使得总利润最大化。
解题思路
这是一个经典的“加权区间调度”(Weighted Interval Scheduling)问题,是动态规划的典型应用。
-
预处理
- 数据结构化:将输入的四个数组
start,end,fleet,profit合并为一个对象/结构体列表,每个对象代表一个贸易机会。 - 过滤:遍历所有贸易机会,剔除那些所需商队规模大于最大限制
C的机会。 - 排序:这是解决此类问题的核心步骤。将所有可行的贸易机会按结束时间
end升序排序。这个排序确保了当我们考虑第i个机会时,所有可能在它之前完成的机会都已经位于0到i-1的位置,这为动态规划提供了最优子结构。
- 数据结构化:将输入的四个数组
-
动态规划
-
DP 状态定义:
dp[i]表示在考虑前i+1个(即索引从0到i的)贸易机会时,所能获得的最大总利润。这个定义允许不选择第i个机会。
-
DP 状态转移:
- 我们从
i = 0到N-1遍历所有排序后的贸易机会。对于第i个机会,我们有两种选择:- 不执行第
i个机会:- 这种情况下,最大利润与只考虑前
i个机会(即索引0到i-1)时是相同的。 - 利润为
dp[i-1](如果i > 0的话)。
- 这种情况下,最大利润与只考虑前
- 执行第
i个机会:- 我们首先获得第
i个机会自身的利润profit[i]。 - 为了最大化总利润,我们还需要加上在第
i个机会开始之前所能积累的最大利润。这意味着我们需要找到一个与第i个机会不冲突的、最优的先前活动序列。 - 具体地,我们需要找到最后一个结束时间不晚于第
i个机会开始时间 (end_time[p] <= start_time[i]) 的机会p。 - 由于列表是按结束时间排序的,我们可以通过二分查找在
0到i-1的范围内高效地找到这个p。 - 如果找到了这样的
p,那么执行第i个机会的总利润就是profit[i] + dp[p]。如果找不到,则总利润就是profit[i]。
- 我们首先获得第
- 不执行第
- 我们从
-
递推公式:
dp[i] = max( 不执行 i 的利润, 执行 i 的利润 )dp[i] = max(dp[i-1], profit[i] + (dp[p] 或 0))
-
-
最终结果
dp数组的最后一个元素dp[N-1]即为所求的最大总利润。
这种 “排序 + DP + 二分查找” 的方法,将时间复杂度从朴素的 优化到了
。
代码
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <algorithm>
#include <map>
using namespace std;
struct Trade {
int start;
int end;
int fleet;
int profit;
};
int main() {
ios::sync_with_stdio(false);
cin.tie(NULL);
string line;
vector<int> starts, ends, fleets, profits;
int val;
getline(cin, line); stringstream ss_starts(line);
while (ss_starts >> val) starts.push_back(val);
getline(cin, line); stringstream ss_ends(line);
while (ss_ends >> val) ends.push_back(val);
getline(cin, line); stringstream ss_fleets(line);
while (ss_fleets >> val) fleets.push_back(val);
getline(cin, line); stringstream ss_profits(line);
while (ss_profits >> val) profits.push_back(val);
int max_fleet;
cin >> max_fleet;
map<int, vector<Trade>> trades_ending_at;
int max_time = 0;
for (size_t i = 0; i < starts.size(); ++i) {
if (fleets[i] <= max_fleet) {
trades_ending_at[ends[i]].push_back({starts[i], ends[i], fleets[i], profits[i]});
max_time = max(max_time, ends[i]);
}
}
vector<vector<long long>> dp(max_time + 1, vector<long long>(max_fleet + 1, 0));
for (int t = 1; t <= max_time; ++t) {
for (int w = 0; w <= max_fleet; ++w) {
dp[t][w] = dp[t - 1][w];
}
if (trades_ending_at.count(t)) {
for (const auto& trade : trades_ending_at[t]) {
int start_time = trade.start;
int fleet_cost = trade.fleet;
int profit_val = trade.profit;
for (int w = max_fleet; w >= fleet_cost; --w) {
dp[t][w] = max(dp[t][w], dp[start_time][w - fleet_cost] + profit_val);
}
}
}
}
long long max_profit = 0;
for (int w = 0; w <= max_fleet; ++w) {
max_profit = max(max_profit, dp[max_time][w]);
}
cout << max_profit << endl;
return 0;
}
import java.util.*;
class Trade {
int start, end, fleet, profit;
public Trade(int start, int end, int fleet, int profit) {
this.start = start;
this.end = end;
this.fleet = fleet;
this.profit = profit;
}
}
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String[] startStrs = sc.nextLine().split(" ");
String[] endStrs = sc.nextLine().split(" ");
String[] fleetStrs = sc.nextLine().split(" ");
String[] profitStrs = sc.nextLine().split(" ");
int maxFleet = Integer.parseInt(sc.nextLine());
Map<Integer, List<Trade>> tradesEndingAt = new HashMap<>();
int maxTime = 0;
for (int i = 0; i < startStrs.length; i++) {
if (startStrs[i].isEmpty()) continue;
int fleet = Integer.parseInt(fleetStrs[i]);
if (fleet <= maxFleet) {
int start = Integer.parseInt(startStrs[i]);
int end = Integer.parseInt(endStrs[i]);
int profit = Integer.parseInt(profitStrs[i]);
tradesEndingAt.putIfAbsent(end, new ArrayList<>());
tradesEndingAt.get(end).add(new Trade(start, end, fleet, profit));
maxTime = Math.max(maxTime, end);
}
}
long[][] dp = new long[maxTime + 1][maxFleet + 1];
for (int t = 1; t <= maxTime; t++) {
for (int w = 0; w <= maxFleet; w++) {
dp[t][w] = dp[t - 1][w];
}
if (tradesEndingAt.containsKey(t)) {
for (Trade trade : tradesEndingAt.get(t)) {
int startTime = trade.start;
int fleetCost = trade.fleet;
int profitVal = trade.profit;
for (int w = maxFleet; w >= fleetCost; w--) {
dp[t][w] = Math.max(dp[t][w], dp[startTime][w - fleetCost] + profitVal);
}
}
}
}
long maxProfit = 0;
for (int w = 0; w <= maxFleet; w++) {
maxProfit = Math.max(maxProfit, dp[maxTime][w]);
}
System.out.println(maxProfit);
}
}
def solve():
starts = list(map(int, input().split()))
ends = list(map(int, input().split()))
fleets = list(map(int, input().split()))
profits = list(map(int, input().split()))
max_fleet = int(input())
trades_ending_at = {}
max_time = 0
for i in range(len(starts)):
if fleets[i] <= max_fleet:
end_time = ends[i]
if end_time not in trades_ending_at:
trades_ending_at[end_time] = []
trades_ending_at[end_time].append((starts[i], fleets[i], profits[i]))
max_time = max(max_time, end_time)
if max_time == 0:
print(0)
return
dp = [[0] * (max_fleet + 1) for _ in range(max_time + 1)]
for t in range(1, max_time + 1):
for w in range(max_fleet + 1):
dp[t][w] = dp[t - 1][w]
if t in trades_ending_at:
for start_time, fleet_cost, profit_val in trades_ending_at[t]:
for w in range(max_fleet, fleet_cost - 1, -1):
dp[t][w] = max(dp[t][w], dp[start_time][w - fleet_cost] + profit_val)
max_profit = 0
if max_time > 0:
max_profit = max(dp[max_time])
print(max_profit)
solve()
算法及复杂度
- 算法: 动态规划 (基于时间轴的背包问题)
- 时间复杂度:
。
是最大结束时间,
是最大商队规模,
是有效贸易机会数量。
- 初始化 DP 表和时间轴遍历是
。
- 处理所有贸易机会,每个机会都需要一次内层背包循环,总共是
。
- 总复杂度由这两部分构成。
- 空间复杂度:
。
- 主要用于存储
状态表。
- 主要用于存储
查看17道真题和解析