Luogu P2240

Luogu P2240

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

题目描述

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

输入格式

第一行两个整数 $N,T$。

接下来 $N$ 行,每行两个整数 $m_i,v_i$。

输出格式

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

输入输出样例 #1

输入 #1

4 50
10 60
20 100
30 120
15 45

输出 #1

240.00

struct sort 示例程序

#include <iostream>
#include <vector>
#include <algorithm>
#include <iomanip>

using namespace std;

struct CoinPile {
    int weight;
    int value;
    double valuePerWeight;
    int index;
};

bool compareCoinPiles(const CoinPile& a, const CoinPile& b) {
    return a.valuePerWeight > b.valuePerWeight;
}

int main() {
    int n, t;
    cin >> n >> t;

    vector<CoinPile> piles(n);
    for (int i = 0; i < n; ++i) {
        cin >> piles[i].weight >> piles[i].value;
        piles[i].valuePerWeight = (double)piles[i].value / piles[i].weight;
        piles[i].index = i;
    }

    // 按照单位价值(价值/重量)从高到低排序 [1]
    sort(piles.begin(), piles.end(), compareCoinPiles);

    double totalValue = 0.0;
    int currentWeight = 0;

    for (int i = 0; i < n; ++i) {
        // 如果当前金币堆可以完全装入背包 [1]
        if (currentWeight + piles[i].weight <= t) {
            totalValue += piles[i].value;
            currentWeight += piles[i].weight;
        } else {
            // 否则,只装入部分,装满背包剩余容量 [1]
            int remainingCapacity = t - currentWeight;
            totalValue += (double)remainingCapacity * piles[i].valuePerWeight;
            currentWeight = t; // 背包已满
            break; // 没有剩余容量了
        }
    }

    cout << fixed << setprecision(2) << totalValue << endl;

    return 0;
}

分析和讲解:

  1. 问题理解 [1]:
    • 这是一个典型的 部分背包问题
    • 目标是 最大化装入背包的金币的总价值 ,同时 不超过背包的最大承重量 $T$ [1]。
    • 题目给出了 $N$ 堆金币,每堆有总重量 $m_i$ 和总价值 $v_i$ [1]。
    • 金币分割后,其 重量价值比(单位价格)保持不变 [1]。
  2. 贪心策略
    • 解决部分背包问题最常用的方法是 贪心策略
    • 贪心策略的核心思想是 优先选择单位价值最高的物品 [1]。也就是说,对于每一堆金币,我们计算其 单位价值 (价值除以重量,即 $v_i / m_i$)。
    • 我们将所有金币堆按照其单位价值从高到低进行排序 [1]。
    • 然后,我们依次考虑每一堆金币:
      • 如果当前背包的剩余容量足够装下整堆金币,我们就将整堆金币装入背包,并更新已装入的总价值和背包的剩余容量。
      • 如果当前背包的剩余容量不足以装下整堆金币,我们就只装入一部分金币,直到背包装满为止。装入的这部分金币的重量等于背包的剩余容量,其价值按照该堆金币的单位价值计算。
    • 由于金币可以任意分割,这种贪心策略能够保证我们获得 最大的总价值 。因为我们总是优先选择性价比最高的金币,直到背包被装满。
  3. C++ 代码实现
    • 数据结构 CoinPile :我们定义了一个结构体 CoinPile 来存储每一堆金币的信息,包括其重量 (weight)、价值 (value)、单位价值 (valuePerWeight) 和原始索引 (index)。计算单位价值是为了方便后续的排序。
    • 排序函数 compareCoinPiles :我们定义了一个比较函数 compareCoinPiles,用于在排序时按照 valuePerWeight 从高到低进行比较。
    • 主函数 main
      • 首先,我们读取输入的金币堆数 $N$ 和背包承重 $T$ [1]。
      • 然后,我们循环读取每堆金币的重量 $m_i$ 和价值 $v_i$,并计算每堆金币的单位价值 [1]。
      • 接着,我们使用 std::sort 函数和自定义的比较函数 compareCoinPiles 对所有金币堆进行排序 [1]。
      • 我们初始化总价值 totalValue0.0 和当前已装入的重量 currentWeight0
      • 我们遍历排序后的金币堆:
      • 如果当前金币堆的重量加上已装入的重量不超过背包容量 $T$,则将整堆金币装入,更新 totalValuecurrentWeight [1]。
      • 否则,计算背包剩余容量 remainingCapacity,然后装入相当于 remainingCapacity 重量的那部分金币,更新 totalValue,并将 currentWeight 设置为 $T$,并跳出循环,因为背包已经装满 [1]。
      • 最后,我们使用 std::fixedstd::setprecision(2) 设置输出格式,保留两位小数,并输出计算得到的最大总价值 totalValue [1]。
  4. 时间复杂度
    • 读取输入的时间复杂度为 O(N)。
    • 计算每堆金币的单位价值的时间复杂度为 O(N)。
    • 对金币堆进行排序的时间复杂度为 O(N log N) ,这主要取决于 std::sort 的实现。
    • 遍历排序后的金币堆并计算总价值的时间复杂度为 O(N)。
    • 因此,整个算法的时间复杂度主要取决于排序步骤,为 O(N log N)

综上所述 [2],提供的 C++ 代码实现了部分背包问题的贪心算法,并通过优先选择单位价值最高的金币来获得最大的总价值。代码中包含了清晰的注释,方便理解其实现逻辑 [2]。