- 问题描述
在商店中,每一种商品都有一个价格(用整数表示)。例如,一朵花的价格是 2 zorkmids (z),而一个花瓶的价格是 5z 。为了吸引更多的顾客,商店举行了促销活动。
促销活动把一个或多个商品组合起来降价销售,例如: 三朵花的价格是 5z 而不是 6z, 两个花瓶和一朵花的价格是 10z 而不是 12z。
编写一个程序,计算顾客购买一定商品的花费,尽量利用优惠使花费最少。
尽管有时候添加其他商品可以获得更少的花费,但是你不能这么做。
对于上面的商品信息,购买三朵花和两个花瓶的最少花费是:以优惠价购买两个花瓶和一朵花(10z),以原价购买两朵花(4z)
- 输入说明
输入包括一些商店提供的优惠信息,接着是购物清单。
第一行 优惠商品的种类数(0 <= s <= 99)。
第二行...第s+1 行 每一行都用几个整数来表示一种优惠方式。
第一个整数 n (1 <= n <= 5),表示这种优惠方式由 n 种商品组成。后面 n 对整数 c 和 k 表示 k (1 <= k <= 5)个编号为 c (1 <= c <= 999)的商品共同构成这种优惠,最后的整数 p 表示这种优惠的优惠价(1 <= p <= 9999)。优惠价总是比原价低。
第 s+2 行 这一行有一个整数 b (0 <= b <= 5),表示需要购买 b 种不同的商品。
第 s+3 行...第 s+b+2 行 这 b 行中的每一行包括三个整数:c ,k ,和 p 。c 表示唯一的商品编号(1 <= c <= 999),k 表示需要购买的 c 商品的数量(1 <= k <= 5)。p 表示 c 商品的原价(1<= p <= 999)。
最多购买 5*5=25 个商品。
- 输出说明
只有一行,输出一个整数:购买这些物品的最低价格。 - 输入范例
cpp
4
2 81 1 62 1 149
2 62 1 113 1 147
2 113 1 34 1 77
2 81 1 34 1 75
4
81 1 27
62 2 135
113 3 27
34 4 56
- 输出范例
cpp
558
感想:说是难度中,实际很难。。。很容易超时。。。优化了多次。
用到了DFS 剪枝、回溯、优惠过滤、排序。
代码如下:
cpp
#include <bits/stdc++.h>
using namespace std;
// 优惠结构体:存储优惠包含的商品数量和优惠价
struct Offer {
vector<int> cnt; // 优惠包含的商品数量(对应商品下标)
int price; // 优惠价格
};
vector<Offer> offers; // 所有有效优惠
vector<int> need_amount; // 需要购买的商品数量(下标对应商品编号映射)
vector<int> original_price; // 商品原价(下标对应商品编号映射)
unordered_map<int, int> c2idx; // 商品编号到数组下标的映射
int min_cost = INT_MAX; // 最小花费
int b; // 需要购买的商品种类数
// 计算剩余商品按原价购买的总花费
int calcOriginalCost(const vector<int>& remain) {
int cost = 0;
for (size_t i = 0; i < remain.size(); ++i) {
cost += remain[i] * original_price[i];
}
return cost;
}
// 深度优先搜索枚举优惠组合,计算最小花费
void dfs(int offer_idx, int current_cost, vector<int>& remain) {
// 剪枝:当前花费已超过最小花费,终止递归
if (current_cost >= min_cost) {
return;
}
// 处理完所有优惠,计算总花费并更新最小值
size_t offers_size = offers.size();
if (offer_idx >= (int)offers_size) {
int total = current_cost + calcOriginalCost(remain);
if (total < min_cost) {
min_cost = total;
}
return;
}
// 不使用当前优惠,处理下一个优惠
dfs(offer_idx + 1, current_cost, remain);
// 尝试使用当前优惠(多次)
const Offer& offer = offers[offer_idx];
int max_use = INT_MAX;
for (size_t i = 0; i < remain.size(); ++i) {
if (offer.cnt[i] == 0) continue;
max_use = min(max_use, remain[i] / offer.cnt[i]);
}
if (max_use == 0) return;
// 回溯法使用优惠,恢复商品数量
for (int k = 1; k <= max_use; ++k) {
for (size_t i = 0; i < remain.size(); ++i) {
remain[i] -= k * offer.cnt[i];
}
int new_cost = current_cost + k * offer.price;
if (new_cost >= min_cost) {
for (size_t i = 0; i < remain.size(); ++i) {
remain[i] += k * offer.cnt[i];
}
break;
}
dfs(offer_idx + 1, new_cost, remain);
// 回溯恢复商品数量
for (size_t i = 0; i < remain.size(); ++i) {
remain[i] += k * offer.cnt[i];
}
}
}
// 判断两个优惠是否重复
bool isDuplicate(const Offer& o1, const Offer& o2) {
if (o1.price != o2.price) return false;
if (o1.cnt.size() != o2.cnt.size()) return false;
for (size_t i = 0; i < o1.cnt.size(); ++i) {
if (o1.cnt[i] != o2.cnt[i]) return false;
}
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int s;
cin >> s;
// 读取原始优惠信息
vector<vector<pair<int, int>>> raw_offers(s);
vector<int> raw_offer_prices(s);
for (int i = 0; i < s; ++i) {
int n;
cin >> n;
for (int j = 0; j < n; ++j) {
int c, k;
cin >> c >> k;
raw_offers[i].push_back(make_pair(c, k));
}
cin >> raw_offer_prices[i];
}
// 读取需要购买的商品信息
cin >> b;
need_amount.resize(b, 0);
original_price.resize(b, 0);
for (int i = 0; i < b; ++i) {
int c, k, p;
cin >> c >> k >> p;
c2idx[c] = i;
need_amount[i] = k;
original_price[i] = p;
}
// 预处理优惠:过滤无效和重复优惠
set<string> offer_hash;
for (int i = 0; i < s; ++i) {
Offer offer;
offer.cnt.resize(b, 0);
bool valid = false;
for (size_t j = 0; j < raw_offers[i].size(); ++j) {
int c = raw_offers[i][j].first;
int k = raw_offers[i][j].second;
if (c2idx.find(c) != c2idx.end()) {
offer.cnt[c2idx[c]] += k;
valid = true;
}
}
offer.price = raw_offer_prices[i];
if (!valid) continue;
string hash_str;
for (size_t j = 0; j < offer.cnt.size(); ++j) {
hash_str += to_string(offer.cnt[j]) + ",";
}
hash_str += to_string(offer.price);
if (offer_hash.find(hash_str) != offer_hash.end()) continue;
offer_hash.insert(hash_str);
// 过滤优惠价不低于原价总和的优惠
int original_sum = 0;
for (size_t j = 0; j < offer.cnt.size(); ++j) {
original_sum += offer.cnt[j] * original_price[j];
}
if (offer.price >= original_sum) continue;
offers.push_back(offer);
}
// 初始化最小花费为全部原价购买的金额
int init_cost = 0;
for (size_t i = 0; i < need_amount.size(); ++i) {
init_cost += need_amount[i] * original_price[i];
}
min_cost = init_cost;
// 按优惠力度排序,优先尝试优惠力度大的优惠
sort(offers.begin(), offers.end(), [&](const Offer& a, const Offer& b) {
int save_a = 0, save_b = 0;
for (size_t i = 0; i < a.cnt.size(); ++i) {
save_a += a.cnt[i] * original_price[i];
}
for (size_t i = 0; i < b.cnt.size(); ++i) {
save_b += b.cnt[i] * original_price[i];
}
save_a -= a.price;
save_b -= b.price;
double ratio_a = (a.price == 0) ? 0 : (double)save_a / a.price;
double ratio_b = (b.price == 0) ? 0 : (double)save_b / b.price;
return ratio_a > ratio_b;
});
// 执行深度优先搜索
vector<int> remain = need_amount;
dfs(0, 0, remain);
// 输出最小花费
cout << min_cost << endl;
return 0;
}