题目链接:www.marscode.cn/practice/w7…
给定n个物品,每个物品的重量为的平方,以及m个查询,每个查询对应一个背包的最大承重。任务是在每个查询的承重限制下,计算最多可以选择的物品数量。
两种解决方案:
- 动态规划:将问题视为0-1背包问题,使用动态规划方法,通过构建状态转移表来计算每个背包容量下的最大物品数;
- 贪心:先对物品重量进行排序,构建前缀和数组,然后对每个查询使用二分搜索来快速确定可选择的最大物品数量。
动态规划
代码
时间复杂度:。
空间复杂度:。
#include <algorithm>
#include <vector>
#include <iostream>
using namespace std;
vector<int> solution(int n, int m, vector<int> a, vector<int> queries) {
const int maxCapacity = *max_element(queries.begin(), queries.end());
sort(a.begin(), a.end());
int maxItems = n;
while (maxItems > 0 and a[maxItems - 1] * a[maxItems - 1] > maxCapacity) {
--maxItems;
}
vector<int> result(m, 0);
if (maxItems == 0) {
return result;
}
// dp[c][i]表示当背包容量为c时,仅考虑前i个排序后的巧克力的情况下,最多能够装多少块巧克力。
vector<vector<int> > dp(maxCapacity + 1, vector<int>(maxItems + 1));
for (int item = 0; item <= maxItems; ++item) {
dp[0][item] = 0;
}
for (int capacity = 1; capacity <= maxCapacity; ++capacity) {
dp[capacity][0] = 0;
}
for (int capacity = 1; capacity <= maxCapacity; ++capacity) {
for (int numItems = 1; numItems <= maxItems; ++numItems) {
const int weight = a[numItems - 1] * a[numItems - 1];
if (weight > capacity) {
dp[capacity][numItems] = dp[capacity][numItems - 1];
} else {
dp[capacity][numItems] = max(dp[capacity][numItems - 1], dp[capacity - weight][numItems - 1] + 1);
}
}
}
for (int qi = 0; qi < m; ++qi) {
result[qi] = dp[queries[qi]][maxItems];
}
return result;
}
int main() {
cout << (solution(5, 5, {1, 2, 2, 4, 5}, {1, 3, 7, 9, 15}) == vector<int>{1, 1, 2, 3, 3}) << endl;
cout << (solution(4, 3, {3, 1, 2, 5}, {5, 10, 20}) == vector<int>{2, 2, 3}) << endl;
cout << (solution(6, 4, {1, 3, 2, 2, 4, 6}, {8, 12, 18, 25}) == vector<int>{2, 3, 4, 4}) << endl;
return 0;
}
思路
这是当成经典的01背包问题来做,每块巧克力重量为边长的平方,价值为1。而相对于经典的01背包问题,本题的动态规划首先对巧克力数组进行了排序,排除了不可能被选中的巧克力(即重量大于最大背包容量的巧克力)。
状态转移方程为:
其中:
- 表示在背包容量为 且仅考虑前 个(索引为)排序后的巧克力时,最多可以装入的巧克力块数。
- 是第 块巧克力的重量。
这个方程的含义是:
- 如果当前考虑的第 块巧克力的重量超过了当前背包的容量 ,则不装入这块巧克力,状态值与不考虑这块巧克力时的状态值相同,即 。
- 否则,可以选择不装入这块巧克力,状态值为 ,或者选择装入这块巧克力,此时状态值为装入这块巧克力后的剩余容量 对应的状态值加上1,即 。两者取最大值作为当前状态值。
前缀和+二分搜索(贪心)
代码
时间复杂度:。排序的时间复杂度为 。对每个查询做二分搜索复杂度为 ,总查询量为 ,因此查询的时间复杂度为。
空间复杂度:。巧克力重量数组和前缀和数组均为,结果数组为。
#include <vector>
#include <iostream>
#include <algorithm>
#include <numeric>
using namespace std;
vector<int> solution(int n, int m, vector<int> a, vector<int> queries) {
// 计算每块巧克力的重量(边长平方)
vector<int> chocolateWeights(n);
transform(a.begin(), a.end(), chocolateWeights.begin(),
[](const int &sideLength)-> int { return sideLength * sideLength; });
// 将巧克力重量排序
sort(chocolateWeights.begin(), chocolateWeights.end());
// 构建前缀和: prefixWeightSums[i] 为最轻的 i 块巧克力重量总和
vector<int> prefixWeightSums(n + 1);
exclusive_scan(chocolateWeights.begin(), chocolateWeights.end(), prefixWeightSums.begin(), 0);
prefixWeightSums[n] = prefixWeightSums[n - 1] + chocolateWeights[n - 1];
// 针对每个背包承重 queries[i],二分搜索可携带的巧克力数量
vector<int> maxChocolateCounts(m);
for (int i = 0; i < m; i++) {
const int &capacity = queries[i];
// 找到 prefixWeightSums 中第一个大于 capacity 的位置
const auto it = upper_bound(prefixWeightSums.begin(), prefixWeightSums.end(), capacity);
int count = static_cast<int>(distance(prefixWeightSums.begin(), it)) - 1;
if (count < 0) {
count = 0; // 容量过小,可能导致计算结果为 -1
}
maxChocolateCounts[i] = count;
}
return maxChocolateCounts;
}
int main() {
cout << (solution(5, 5, {1, 2, 2, 4, 5}, {1, 3, 7, 9, 15}) == vector<int>{1, 1, 2, 3, 3}) << endl;
cout << (solution(4, 3, {3, 1, 2, 5}, {5, 10, 20}) == vector<int>{2, 2, 3}) << endl;
cout << (solution(6, 4, {1, 3, 2, 2, 4, 6}, {8, 12, 18, 25}) == vector<int>{2, 3, 4, 4}) << endl;
return 0;
}
思路
这道题的特殊之处在于每个物品的价值为1。因此可以采用贪心的做法。思路如下:
- 将边长转换为重量。 先对原数组
a做一次映射,得到每块巧克力的重量数组chocolateWeights。 - 排序。 将得到的巧克力重量数组从小到大排序。
- 构建前缀和。 构建一个长度为
n + 1的前缀和数组prefixWeightSums,其中prefixWeightSums[0] = 0,prefixWeightSums[i] = prefixWeightSums[i - 1] + chocolateWeights[i - 1]。这样prefixWeightSums[i]就表示最轻的i块巧克力的总重量。 - 处理查询。 对于每个查询(最大承重
w),采用二分搜索在前缀和数组中找出最大的i,使得prefixWeightSums[i] <= w。