[洛谷]P2240 【深基12.例1】部分背包问题

洛谷P2240:部分背包问题解析与解决方案
阿里巴巴面临一个部分背包问题,需要在承重量限制下最大化装走金币的总价值。每堆金币可任意分割,保持单位价格不变。问题通过计算每堆金币的单位价值并按降序排列,优先选取单位价值高的金币装入背包来解决。给出的代码实现了这一策略。

题目描述

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

输入格式

第一行两个整数 N、T
接下来 N 行,每行两个整数 m_i,v_imi​,vi​

输出格式

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

输入输出样例

输入 #1复制

4 50
10 60
20 100
30 120
15 45

输出 #1复制

240.00

【思路】

因为金币可以任意分割,我们不用担心因为背包的体积, 放入一个背包后, 无法放入其他背包。

步骤如下, 我们根据每组背包的总价值和体积,计算出每组背包的单位价格。并按照单位价格排序。

然后优先放入单位价值高的金币。直到背包容量为空为止。(因为背包可以分割,所以,如果背包剩余空间放不下一组的话,可以把这一组的金币分开存放。)

【代码】

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
struct Good {
    int w, v;
    double p;
    bool operator<(const Good & a) const {
        return p > a.p;
    }
};
const int  N = 105;
Good a[N];
int n, t;
double res;
int main() {
    cin >> n >> t;
    for(int i = 0 ;i < n; i++) {
        scanf("%d%d", &a[i].w, &a[i].v);
        a[i].p = double(a[i].v) / a[i].w;
    }
    sort(a, a + n);
    for(int i = 0 ;i < n; i++) {
        if(t > a[i].w) {
            res += a[i].v;
            t -= a[i].w;
        } else {
            res += t * a[i].p;
            break;
        }
    }
    printf("%.2lf", res);
    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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值