#P3898. 第1题-星际快递
-
1000ms
Tried: 42
Accepted: 14
Difficulty: 5
所属公司 :
京东
时间 :2025年10月11日
-
算法标签>动态规划
第1题-星际快递
解题思路
本题是一个变形的背包问题:每个包裹可以选择两种“花费”(燃料),其中一种会额外消耗 1 张通行证。目标是先最大化派送包裹数,在此基础上最小化燃料消耗,并满足燃料 ≤ X、通行证 ≤ Y。
算法:动态规划(DP,二元约束下的最优化-可行性分离)
-
设计状态: 设
dp[j][k]表示在处理若干包裹后,恰好派送 j 个包裹、使用 k 张通行证时的最小燃料。 初始化:dp[0][0] = 0,其余为正无穷。 -
转移(对每个包裹的常规/虫洞两种选择):
- 常规派送(不耗通行证):
dp[j+1][k] = min(dp[j+1][k], dp[j][k] + a_i) - 虫洞派送(耗 1 张通行证):
若
k+1 ≤ Y,dp[j+1][k+1] = min(dp[j+1][k+1], dp[j][k] + b_i)
- 常规派送(不耗通行证):
-
为避免同一轮中重复使用更新后的值,
j必须从大到小循环(k也可从大到小循环)。 -
扫描答案:从
j = N..0递减,找到满足min_k dp[j][k] ≤ X的最大j;对应最小燃料为该j下所有k中的最小dp[j][k]。
该思路将“最大化数量、次级最小化燃料”的字典序目标自然体现在状态与转移里:先按 j 优先级搜索可行解,再在同一 j 内取最小燃料。
复杂度分析
- 状态规模:
(N+1) × (Y+1)。 - 每个包裹进行一次两种转移,内层遍历
j ∈ [0..i]与k ∈ [0..Y],因此总时间复杂度 O(N × N × Y)(j上界随已处理包裹数增长,整体 ≤N^2 Y)。在数据范围N ≤ 100, Y < 50下约 50 万级别,完全可行。 - 空间复杂度:O(N × Y)。
代码实现
Python
# -*- coding: utf-8 -*-
# 题意:在燃料和通行证限制下,优先最大化派送包裹数,其次最小化燃料
# 算法:dp[j][k] = 派送j个、用k张通行证的最小燃料;每个包裹两种转移,j与k倒序更新
import sys
INF = 10**9
def solve():
data = sys.stdin.read().strip().split()
it = iter(data)
N = int(next(it)); X = int(next(it)); Y = int(next(it))
items = []
for _ in range(N):
a = int(next(it)) # 常规燃料
b = int(next(it)) # 虫洞燃料(耗1张票)
items.append((a, b))
# dp[j][k] = 最小燃料
dp = [[INF] * (Y + 1) for _ in range(N + 1)]
dp[0][0] = 0
for a, b in items:
# j从大到小,防止同一物品被重复计入
for j in range(N - 1, -1, -1):
# k从大到小,安全避免本轮污染
for k in range(Y, -1, -1):
if dp[j][k] == INF:
continue
# 常规派送,不耗通行证
if dp[j][k] + a < dp[j + 1][k]:
dp[j + 1][k] = dp[j][k] + a
# 虫洞派送,耗1张通行证
if k + 1 <= Y and dp[j][k] + b < dp[j + 1][k + 1]:
dp[j + 1][k + 1] = dp[j][k] + b
# 找到最大可派送数与对应最小燃料
best_cnt = 0
best_fuel = 0
for j in range(N, -1, -1):
cur = min(dp[j]) # 在所有通行证使用量中取最小燃料
if cur <= X:
best_cnt = j
best_fuel = cur
break
print(best_cnt, best_fuel)
if __name__ == "__main__":
solve()
Java
// 题意:在燃料与通行证限制下,先最大化派送数量,再最小化燃料
// 算法:dp[j][k] 表示派送 j 个、用 k 张通行证的最小燃料;每个包裹两种选择,j、k 倒序更新
import java.util.*;
public class Main {
static final int INF = 1_000_000_000;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt(), X = sc.nextInt(), Y = sc.nextInt();
int[] A = new int[N];
int[] B = new int[N];
for (int i = 0; i < N; i++) {
A[i] = sc.nextInt(); // 常规燃料
B[i] = sc.nextInt(); // 虫洞燃料(耗1张通行证)
}
// dp[j][k] = 最小燃料
int[][] dp = new int[N + 1][Y + 1];
for (int i = 0; i <= N; i++) Arrays.fill(dp[i], INF);
dp[0][0] = 0;
// 逐个包裹处理
for (int i = 0; i < N; i++) {
int a = A[i], b = B[i];
for (int j = N - 1; j >= 0; j--) {
for (int k = Y; k >= 0; k--) {
if (dp[j][k] == INF) continue;
// 常规派送
if (dp[j][k] + a < dp[j + 1][k]) {
dp[j + 1][k] = dp[j][k] + a;
}
// 虫洞派送(多用1张通行证)
if (k + 1 <= Y && dp[j][k] + b < dp[j + 1][k + 1]) {
dp[j + 1][k + 1] = dp[j][k] + b;
}
}
}
}
int bestCnt = 0, bestFuel = 0;
for (int j = N; j >= 0; j--) {
int cur = INF;
for (int k = 0; k <= Y; k++) cur = Math.min(cur, dp[j][k]);
if (cur <= X) {
bestCnt = j;
bestFuel = cur;
break;
}
}
System.out.println(bestCnt + " " + bestFuel);
}
}
C++
// 题意:在燃料与通行证约束下,优先最大化派送包裹数,并使燃料最小
// 算法:dp[j][k] = 派送j个、用k张通行证的最小燃料;对每个包裹进行两种转移,j/k倒序
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, X, Y;
if (!(cin >> N >> X >> Y)) return 0;
vector<int> A(N), B(N);
for (int i = 0; i < N; ++i) {
cin >> A[i] >> B[i]; // A:常规燃料,B:虫洞燃料(耗1张通行证)
}
// dp[j][k] = 最小燃料
vector<vector<int>> dp(N + 1, vector<int>(Y + 1, INF));
dp[0][0] = 0;
for (int i = 0; i < N; ++i) {
int a = A[i], b = B[i];
for (int j = N - 1; j >= 0; --j) {
for (int k = Y; k >= 0; --k) {
if (dp[j][k] == INF) continue;
// 常规派送
dp[j + 1][k] = min(dp[j + 1][k], dp[j][k] + a);
// 虫洞派送(多用1张通行证)
if (k + 1 <= Y) {
dp[j + 1][k + 1] = min(dp[j + 1][k + 1], dp[j][k] + b);
}
}
}
}
int bestCnt = 0, bestFuel = 0;
for (int j = N; j >= 0; --j) {
int cur = INF;
for (int k = 0; k <= Y; ++k) cur = min(cur, dp[j][k]);
if (cur <= X) {
bestCnt = j;
bestFuel = cur;
break;
}
}
cout << bestCnt << " " << bestFuel << "\n";
return 0;
}
题目内容
星际快递公司有 N 个包裹需要派送,每个包裹有两种派送方式!
1、常规派送(消耗较多燃料)
2、虫洞派送(使用一个虫洞通行证,可以以消耗较少燃料的情况下完成派送)
当前快递飞船携带了 X 单位的燃料和 Y 张虫洞通行证。
星际快递公司想要计算,在优先派送尽可能多包裹的情况下,最小的燃料消耗是多少,请你帮助他们计算一下。
输入描述
第一行三个整数 N,X,Y ,分别表示包裹数量,携带燃料量以及通行证数量。
接下来 N 行,每行两个整数,表示各个包裹常规派送和虫洞派送分别需要的燃料量
1≤N≤100,1≤X≤5000,1≤Y<50 ,每个包裹常规派送和虫洞派送的燃料消耗均介于 [1,50] 之间
输出描述
一行,两个整数,空格分开,表示最多可派送的包裹数量及对应的最小燃料消耗。
样例1
输入
3 20 1
8 5
7 4
10 6
输出
2 12
样例2
输入
4 25 2
10 6
8 5
12 7
9 6
输出
3 20