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