按仓库取价值最高的前 k 件货物,将每个仓库视为多重选择组,再以总费用为容量做背包。
OJ: shumeng
题目 ID: CSP202406D
难度:提高+/省选-
标签:背包分组背包排序
日期: 2026-07-31 16:21
形式化题目
有
求满足“卖出的货物总价值 - 总费用
思路
先观察一个简化事实:同一仓库决定卖
朴素做法:01 序列枚举每件货物
先看最直接的做法,用 01 选择序列枚举每件货物卖或不卖,到叶子节点统一检查方案是否满足目标。
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-31 16:21
* update_at: 2026-08-17 22:39
*/
// brute.cpp:小数据暴力解,用 01 选择序列枚举每件货物卖或不卖,只适合 m 很小的数据。
#include <bits/stdc++.h>
using namespace std;
int n, m, target;
vector<int> basic_cost, piece_cost, value, warehouse;
vector<int> choose_item; // choose_item[i] 表示第 i 件货物:0 不卖,1 卖
int answer;
// 检查当前完整的 01 选择序列对应的方案是否满足目标
void check_choice() {
vector<int> count(n, 0);
vector<int> total_value(n, 0);
for (int i = 0; i < m; i++) {
if (choose_item[i] == 0) continue;
count[warehouse[i]]++;
total_value[warehouse[i]] += value[i];
}
int profit = 0;
int cost = 0;
for (int i = 0; i < n; i++) {
if (count[i] == 0) continue;
cost += basic_cost[i] + count[i] * piece_cost[i];
profit += total_value[i] - basic_cost[i] - count[i] * piece_cost[i];
}
if (profit >= target) answer = min(answer, cost);
}
// 第 index 件货物只有卖/不卖两种选择
void enumerate_choice(int index) {
if (index == m) {
check_choice();
return;
}
choose_item[index] = 0;
enumerate_choice(index + 1);
choose_item[index] = 1;
enumerate_choice(index + 1);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> target;
basic_cost.resize(n);
piece_cost.resize(n);
for (int i = 0; i < n; i++) cin >> basic_cost[i] >> piece_cost[i];
value.resize(m);
warehouse.resize(m);
for (int i = 0; i < m; i++) cin >> value[i] >> warehouse[i];
choose_item.assign(m, 0);
answer = 1000000000;
enumerate_choice(0);
cout << answer << '\n';
return 0;
}枚举所有
主解:分组背包
对每个仓库,把货物价值降序排序。若该仓库选前
净现金为前
设 dp[c] 表示已经处理完若干仓库、恰好花费 dp 表示“不选该仓库”,再枚举选择前 dp 转移。同一仓库的不同选项互斥,所以必须从旧状态转移而不是滚动覆盖。
费用上界
由于
可以按费用开数组,最后扫描满足 dp[c] >= v 的最小
代码
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-07-31 16:21
* update_at: 2026-08-17 22:39
*/
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
const int MAXC = 40005;
int n, m, target;
int basic_cost[MAXN]; // 仓库 i 的基本运费
int piece_cost[MAXN]; // 仓库 i 每件货物的计件运费
vector<int> goods[MAXN]; // goods[i] 存放仓库 i 中所有货物的价值
int dp[MAXC]; // dp[c] 表示恰好花费 c 时能获得的最大净现金
int next_dp[MAXC]; // 处理当前仓库时的滚动数组
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> target;
for (int i = 0; i < n; i++) cin >> basic_cost[i] >> piece_cost[i];
for (int i = 0; i < m; i++) {
int value, warehouse;
cin >> value >> warehouse;
goods[warehouse].push_back(value);
}
// 同一仓库选 k 件时应取价值最高的 k 件,先降序排序;同时算出最大可能花费
int maximum_cost = 0;
for (int i = 0; i < n; i++) {
if (goods[i].empty()) continue;
sort(goods[i].begin(), goods[i].end(), greater<int>());
maximum_cost += basic_cost[i] + piece_cost[i] * (int)goods[i].size();
}
// 分组背包:把每个仓库看作一组,组内选项是“选前 k 件”的所有 k
const int NEG_INF = -1000000000;
for (int c = 0; c <= maximum_cost; c++) dp[c] = NEG_INF;
dp[0] = 0;
for (int warehouse = 0; warehouse < n; warehouse++) {
if (goods[warehouse].empty()) continue;
// next_dp 先复制 dp,表示“不选这个仓库”的情况
for (int c = 0; c <= maximum_cost; c++) next_dp[c] = dp[c];
int profit = -basic_cost[warehouse];
for (int count = 1; count <= (int)goods[warehouse].size(); count++) {
profit += goods[warehouse][count - 1] - piece_cost[warehouse]; // 选前 count 件的净现金
int cost = basic_cost[warehouse] + count * piece_cost[warehouse];
// 从旧 dp 转移,保证同一仓库的不同选项互斥
for (int old_cost = 0; old_cost + cost <= maximum_cost; old_cost++) {
if (dp[old_cost] == NEG_INF) continue;
if (next_dp[old_cost + cost] < dp[old_cost] + profit) {
next_dp[old_cost + cost] = dp[old_cost] + profit;
}
}
}
for (int c = 0; c <= maximum_cost; c++) dp[c] = next_dp[c];
}
// 找满足净现金 >= target 的最小花费
int answer = maximum_cost;
for (int c = 0; c <= maximum_cost; c++) {
if (dp[c] >= target && c < answer) answer = c;
}
cout << answer << '\n';
return 0;
}复杂度
设货物总数
- 时间:每个仓库的选项总数为
,分组转移 ,排序 。 - 空间:DP 数组
,货物存储 。
总结
把“同仓库选哪些货物”先压缩为“选前