题目描述
你被一家黄铜砖工厂的几位客户雇佣。黄铜是铜和锌的合金;每块砖重 100010001000 克,其中铜的含量可以在 111 到 999999999 克之间。工厂通过各种工艺生产不同类型的砖,每种类型的铜浓度和价格不同。
你的客户希望购买一定数量 MMM 的砖,这些砖必须是不同类型的。它们将被熔化在一起,混合后的铜浓度必须在每千克 CMinCMinCMin 到 CMaxCMaxCMax 克之间。他们的目标是选择 MMM 种类型的砖,使得混合后的浓度符合要求,并且总价格最小。
输入格式
- 第一行:测试用例数量
- 每个测试用例:
- 第一行:砖类型数量 NNN (1≤N≤2001 \leq N \leq 2001≤N≤200)
- 接下来 NNN 行:每行包含铜浓度和价格(美分)
- 接下来一行:客户数量 CCC (1≤C≤1001 \leq C \leq 1001≤C≤100)
- 接下来 CCC 行:每行包含 MMM, CMinCMinCMin, CMaxCMaxCMax (1≤M≤201 \leq M \leq 201≤M≤20)
输出格式
对于每个客户,输出满足要求的最小价格,如果无法满足则输出 impossible。
题目分析
问题转化
每块砖重 100010001000 克,铜含量为 concentrationconcentrationconcentration 克。当选择 MMM 块砖时:
- 总铜量 = 所选砖的铜含量之和
- 总重量 = M×1000M \times 1000M×1000 克
- 平均浓度 = 总铜量 / MMM(克/千克)
要求平均浓度在 [CMin,CMax][CMin, CMax][CMin,CMax] 范围内,等价于:
总铜量 ∈[M×CMin,M×CMax]\in [M \times CMin, M \times CMax]∈[M×CMin,M×CMax]
因此,问题转化为:从 NNN 个物品中选择恰好 MMM 个,使得它们的铜含量之和在 [L,R][L, R][L,R] 范围内(其中 L=M×CMinL = M \times CMinL=M×CMin, R=M×CMaxR = M \times CMaxR=M×CMax),并且总价格最小。
动态规划解法
这是一个典型的背包问题变种,我们需要考虑三个维度:
- 选择的砖块数量
- 铜含量总和
- 最小价格
状态定义
设 dp[i][j]dp[i][j]dp[i][j] 表示选择 iii 块砖,铜含量总和为 jjj 的最小价格。
状态初始化
- dp[0][0]=0dp[0][0] = 0dp[0][0]=0(选择 000 块砖,铜含量为 000 的价格为 000)
- 其他状态初始化为无穷大(表示不可达)
状态转移
对于每块砖(铜含量 ccc,价格 ppp),我们采用倒序更新的方式避免重复选择同一块砖:
for k = 0 to n-1: // 遍历每块砖
for i = m down to 1: // 倒序遍历选择的砖块数量
for j = high down to c: // 倒序遍历铜含量总和
if dp[i-1][j-c] != INF:
dp[i][j] = min(dp[i][j], dp[i-1][j-c] + p)
答案提取
在所有满足 L≤j≤RL \leq j \leq RL≤j≤R 的 dp[M][j]dp[M][j]dp[M][j] 中找最小值。
复杂度分析
- 砖块类型数:N≤200N \leq 200N≤200
- 最大选择数:M≤20M \leq 20M≤20
- 铜含量最大和:20×999≈2000020 \times 999 \approx 2000020×999≈20000
- 时间复杂度:O(N×M×R)O(N \times M \times R)O(N×M×R),在题目约束下可行
代码实现
// The Brick Stops Here
// UVa ID: 10373
// Verdict: Accepted
// Submission Date: 2025-11-25
// UVa Run Time: 4.850s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> copper(n), price(n);
for (int i = 0; i < n; i++)
cin >> copper[i] >> price[i];
int clients;
cin >> clients;
while (clients--) {
int m, cmin, cmax;
cin >> m >> cmin >> cmax;
int low = m * cmin;
int high = m * cmax;
vector<vector<int>> dp(m + 1, vector<int>(high + 1, INF));
dp[0][0] = 0;
for (int k = 0; k < n; k++) {
for (int i = m; i >= 1; i--) {
for (int j = high; j >= copper[k]; j--) {
if (dp[i - 1][j - copper[k]] != INF)
dp[i][j] = min(dp[i][j], dp[i - 1][j - copper[k]] + price[k]);
}
}
}
int ans = INF;
for (int j = low; j <= high; j++)
ans = min(ans, dp[m][j]);
if (ans == INF) cout << "impossible" << endl;
else cout << ans << endl;
}
if (t) cout << endl;
}
return 0;
}
优化实现:
// The Brick Stops Here
// UVa ID: 10373
// Verdict: Accepted
// Submission Date: 2025-11-25
// UVa Run Time: 2.200s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> copper(n), price(n);
for (int i = 0; i < n; i++)
cin >> copper[i] >> price[i];
int clients;
cin >> clients;
while (clients--) {
int m, cmin, cmax;
cin >> m >> cmin >> cmax;
int low = m * cmin;
int high = m * cmax;
if (low > high) {
cout << "impossible" << endl;
continue;
}
// 优化:限制状态空间大小
int maxSum = min(high, m * 999);
vector<vector<int>> dp(m + 1, vector<int>(maxSum + 1, INF));
dp[0][0] = 0;
int bestPrice = INF;
for (int k = 0; k < n; k++) {
int c = copper[k];
int p = price[k];
// 价格剪枝:如果当前砖块价格已经超过已知最优解,跳过
if (p >= bestPrice) continue;
for (int i = m - 1; i >= 0; i--) {
for (int j = min(maxSum - c, (i + 1) * 999); j >= 0; j--) {
if (dp[i][j] != INF) {
int newSum = j + c;
int newPrice = dp[i][j] + p;
// 价格剪枝
if (newPrice >= bestPrice) continue;
if (newSum <= maxSum) {
dp[i + 1][newSum] = min(dp[i + 1][newSum], newPrice);
// 如果找到完整解,更新最优解
if (i + 1 == m && newSum >= low) {
bestPrice = min(bestPrice, newPrice);
}
}
}
}
}
}
// 最终检查
for (int j = low; j <= maxSum; j++) {
if (dp[m][j] != INF) {
bestPrice = min(bestPrice, dp[m][j]);
}
}
if (bestPrice == INF) cout << "impossible" << endl;
else cout << bestPrice << endl;
}
if (t) cout << endl;
}
return 0;
}
关键点总结
- 问题转化:将浓度范围约束转化为铜含量总和的范围约束
- 动态规划设计:使用三维状态(数量、铜含量、价格)的背包问题
- 倒序更新:确保每块砖最多被选择一次
- 边界处理:正确处理无解情况和多个测试用例的输出格式
这种方法能够高效地解决题目要求,在给定的约束条件下找到最优解。
6万+

被折叠的 条评论
为什么被折叠?



