P2240 【深基12.例1】部分背包问题

本文探讨了阿里巴巴如何利用贪心策略解决部分背包问题,通过构建结构体并按权重排序实现价值最大化。

【深基12.例1】部分背包问题

题目描述

阿里巴巴走进了装满宝藏的藏宝洞。藏宝洞里面有 N(N≤100)N(N \le 100)N(N100) 堆金币,第 iii 堆金币的总重量和总价值分别是 mi,vi(1≤mi,vi≤100)m_i,v_i(1\le m_i,v_i \le 100)mi,vi(1mi,vi100)。阿里巴巴有一个承重量为 T(T≤1000)T(T \le 1000)T(T1000) 的背包,但并不一定有办法将全部的金币都装进去。他想装走尽可能多价值的金币。所有金币都可以随意分割,分割完的金币重量价值比(也就是单位价格)不变。请问阿里巴巴最多可以拿走多少价值的金币?

输入格式

第一行两个整数 N,TN,TN,T

接下来 NNN 行,每行两个整数 mi,vim_i,v_imi,vi

输出格式

一个实数表示答案,输出两位小数

样例 #1

样例输入 #1

4 50
10 60
20 100
30 120
15 45

样例输出 #1

240.00

经典的贪心,首先想到用结构体存储重量,价值和权重,以权重排序,然后就是从高到低装了
套路不多,代码奉上

#include <iostream>  
using namespace std;

struct f {
    int m;
    int v;
    double q;
};

double h(struct f s[], int z, int n) { // 注意:函数返回类型应该是double  
    double g = 0;
    for (int i = 0; i < n&&z>=0; i++) {
        if (s[i].m > z) {
            g += z * s[i].q;// 应该是g += 而不是g = 
			return g;  
        }
        else {
            g += s[i].v;
            z -= s[i].m; // 这里应该是s[i].m而不是m  
        }
    }
    return g; // 返回计算结果  
}

int main() {
    int n, z;
    cin >> n >> z;
    struct f s[n];
    for (int i = 0; i < n; i++) {
        cin >> s[i].m >> s[i].v;
        s[i].q = static_cast<double>(s[i].v) / s[i].m; // 使用s[i].v来计算q  
    }

    // 排序,使用结构体成员进行交换  
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            if (s[i].q < s[j].q) {
                struct f temp = s[i]; // 使用临时变量temp进行交换  
                s[i] = s[j];
                s[j] = temp;
            }
        }
    }

    // 调用h函数并打印结果  
   printf("%.2lf", h(  s,  z,  n));

    return 0;
}
<think>我们正在处理LGP224012.1部分背包问题。这是一个经典的贪心算法问题,通常出现在算法学习教程中。 问题描述:有n堆金币,第i堆金币的总重量和总价值分别是m_i和v_i。现在有一个背包,它能装的最大重量为T。每堆金币可以被任意分割,分割后的价值和重量按比计算。求背包中最多能装多少价值的金币? 贪心策略:按照单位重量的价值(即v_i/m_i)从大到小排序,然后依次选取,直到背包装满。 步骤: 1. 计算每堆金币的单位价值:v_i/m_i。 2. 按单位价值从高到低排序。 3. 依次将单位价值高的金币堆放入背包,如果该堆金币全部放入背包不会超重,则全部放入;否则,只取部分(取背包剩余重量的部分)即可。 代码实现(使用C++,因为题目来源可能是洛谷,且系列通常使用C++): 注意:由于是部分背包,我们可以采用结构体或pair来存储每堆金币的重量、价值以及单位价值。 伪代码1. 输入:n(金币堆数)和T(背包容量) 2. 输入n堆金币,每堆的重量m_i和价值v_i 3. 计算每堆的单位价值 = v_i / m_i 4. 按照单位价值降序排序 5. 初始化当前背包已装重量为0,总价值为0 6. 遍历排序后的金币堆: if (当前堆的重量 <= 剩余背包容量) { 全部装入:总价值加上这堆金币的价值,剩余背包容量减去这堆的重量 } else { 只装入剩余背包容量那么多:总价值加上 (剩余背包容量 * 单位价值),然后剩余背包容量变为0,退出循环 } 下面给出C++代码实现: ```cpp #include <iostream> #include <algorithm> #include <iomanip> using namespace std; struct Coin { double m, v; // m:重量, v:价值 double unit; // 单位价值 }; bool cmp(Coin a, Coin b) { return a.unit > b.unit; } int main() { int n; double T; cin >> n >> T; Coin coins[105]; // 题目中n<=100,所以数组大小设为105 for (int i = 0; i < n; i++) { cin >> coins[i].m >> coins[i].v; coins[i].unit = coins[i].v / coins[i].m; } sort(coins, coins + n, cmp); double totalValue = 0.0; double remaining = T; // 剩余背包容量 for (int i = 0; i < n; i++) { if (remaining <= 0) break; if (coins[i].m <= remaining) { // 全部装入 totalValue += coins[i].v; remaining -= coins[i].m; } else { // 只取部分 totalValue += coins[i].unit * remaining; remaining = 0; break; // 背包已满,可以退出 } } cout << fixed << setprecision(2) << totalValue << endl; return 0; } ``` 注意:输出要求保留两位小数。 问题解析: 1. 为什么贪心算法适用于部分背包问题? 因为物品可以任意分割,所以我们可以按照单位价值从高到低选取,这样能保证每一步都获取当前最优解,从而整体最优。 2. 与0-1背包问题的区别:0-1背包问题中物品不可分割,所以贪心策略(按单位价值排序)并不总是最优,但部分背包问题可以使用贪心。 引用说明:部分背包问题是贪心算法的经典应用,在许多算法教材中都有提及[^1]。 §§相关问题§§ 1. 部分背包问题与0-1背包问题的区别是什么? 2. 如果部分背包问题要求输出具体选取了哪些金币堆以及每堆选取的比,如何修改代码? 3. 贪心算法背包问题中的应用还有哪些变体? </think>### LGP224012.1部分背包问题解析及解决方案 #### 问题描述 给定 $n$ 堆金币,第 $i$ 堆总重量为 $m_i$,总价值为 $v_i$。背包最大承重 $T$。每堆金币可任意分割(按比计算价值),求背包能装载的最大价值。 本质是典型的**贪心算法**问题,与0-1背包的核心区别在于物品可分割[^1]。 #### 贪心策略 1. **核心思想**:优先选择单位价值最高的金币 计算单位价值 $\varphi_i = \frac{v_i}{m_i}$ 2. **排序规则**:按 $\varphi_i$ **降序排列**金币堆 3. **装载规则**: - 若当前金币堆重量 $\leq$ 背包剩余容量 → **全装入** - 否则取剩余容量的比部分 → **部分装入** #### 算法正确性证明 通过反证法:假设存在更优方案,则必有单位价值更低的金币被优先选取,与贪心策略矛盾[^1]。 #### 代码实现(C++) ```cpp #include <iostream> #include <algorithm> #include <iomanip> using namespace std; struct Coin { double m, v; // m:重量, v:价值 double unit; // 单位价值 }; bool cmp(Coin a, Coin b) { return a.unit > b.unit; // 按单位价值降序排序 } int main() { int n; double T; cin >> n >> T; Coin coins[105]; for (int i = 0; i < n; i++) { cin >> coins[i].m >> coins[i].v; coins[i].unit = coins[i].v / coins[i].m; } sort(coins, coins + n, cmp); double totalValue = 0.0; for (int i = 0; i < n && T > 0; i++) { if (coins[i].m <= T) { // 全装入 totalValue += coins[i].v; T -= coins[i].m; } else { // 部分装入 totalValue += T * coins[i].unit; T = 0; } } cout << fixed << setprecision(2) << totalValue; return 0; } ``` #### 复杂度分析 - 时间复杂度:$O(n \log n)$(排序主导) - 空间复杂度:$O(n)$ #### 注意事项 1. 输入输出需保留2位小数(`setprecision(2)`) 2. 金币堆数 $n \leq 100$,无需考虑大数问题 3. 边界情况:当 $T=0$ 或所有金币重量为0时直接输出0.00
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值