东华OJ-基础题-118-商店购物(C++)-难度中

  • 问题描述
    在商店中,每一种商品都有一个价格(用整数表示)。例如,一朵花的价格是 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;
}
相关推荐
寻寻觅觅☆6 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
fpcc6 小时前
并行编程实战——CUDA编程的Parallel Task类型
c++·cuda
偷吃的耗子7 小时前
【CNN算法理解】:三、AlexNet 训练模块(附代码)
深度学习·算法·cnn
化学在逃硬闯CS7 小时前
Leetcode1382. 将二叉搜索树变平衡
数据结构·算法
ceclar1238 小时前
C++使用format
开发语言·c++·算法
Gofarlic_OMS8 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
lanhuazui108 小时前
C++ 中什么时候用::(作用域解析运算符)
c++
charlee448 小时前
从零实现一个生产级 RAG 语义搜索系统:C++ + ONNX + FAISS 实战
c++·faiss·onnx·rag·语义搜索
夏鹏今天学习了吗8 小时前
【LeetCode热题100(100/100)】数据流的中位数
算法·leetcode·职场和发展
老约家的可汗8 小时前
初识C++
开发语言·c++