UVa 10373 The Brick Stops Here

题目描述

你被一家黄铜砖工厂的几位客户雇佣。黄铜是铜和锌的合金;每块砖重 100010001000 克,其中铜的含量可以在 111999999999 克之间。工厂通过各种工艺生产不同类型的砖,每种类型的铜浓度和价格不同。

你的客户希望购买一定数量 MMM 的砖,这些砖必须是不同类型的。它们将被熔化在一起,混合后的铜浓度必须在每千克 CMinCMinCMinCMaxCMaxCMax 克之间。他们的目标是选择 MMM 种类型的砖,使得混合后的浓度符合要求,并且总价格最小。

输入格式

  • 第一行:测试用例数量
  • 每个测试用例:
    • 第一行:砖类型数量 NNN (1≤N≤2001 \leq N \leq 2001N200)
    • 接下来 NNN 行:每行包含铜浓度和价格(美分)
    • 接下来一行:客户数量 CCC (1≤C≤1001 \leq C \leq 1001C100)
    • 接下来 CCC 行:每行包含 MMM, CMinCMinCMin, CMaxCMaxCMax (1≤M≤201 \leq M \leq 201M20)

输出格式

对于每个客户,输出满足要求的最小价格,如果无法满足则输出 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 RLjRdp[M][j]dp[M][j]dp[M][j] 中找最小值。

复杂度分析

  • 砖块类型数:N≤200N \leq 200N200
  • 最大选择数:M≤20M \leq 20M20
  • 铜含量最大和:20×999≈2000020 \times 999 \approx 2000020×99920000
  • 时间复杂度: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;
}

关键点总结

  1. 问题转化:将浓度范围约束转化为铜含量总和的范围约束
  2. 动态规划设计:使用三维状态(数量、铜含量、价格)的背包问题
  3. 倒序更新:确保每块砖最多被选择一次
  4. 边界处理:正确处理无解情况和多个测试用例的输出格式

这种方法能够高效地解决题目要求,在给定的约束条件下找到最优解。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值