题解:学而思编程 假币购物
【题目来源】
学而思编程:假币购物
【题目描述】
战争时期,A国制造了大量敌国B国的假币,由间谍拿着假币到B国购买大宗商品。
B国的货币的面值有 \(n\) 种,第 \(i\) 种的面值为 \(m_i\),A国制造一张这样的假币的花费为 \(c_i\)。
在B国有 \(k\) 件大宗商品可以购买,第 \(j\) 件商品的价格是 \(a_j\) ,间谍购买时会支付总面值恰好等于价格的假币。
在造币总花费不超过 \(W\) 的前提下,最多能买到的总价格是多少?
【输入】
第 \(1\) 行,\(3\) 个正整数 \(n,k,W\)
接下来 \(n\) 行,每行 \(2\) 个正整数 \(m_i,c_i\),表示制造面值 \(m_i\) 的假币需要的花费 \(c_i\)
最后一行,\(k\) 个正整数 \(a_1,a_2,⋯,a_k\),表示可以买的商品的价格。
【输出】
\(1\) 个整数,最多能买到的总价格。
【输入样例】
5 5 30
10 7
7 2
6 3
9 5
1 9
11 2 32 22 27
【输出样例】
59
【核心思想】
-
问题分析:给定 \(n\) 种假币面值 \(m_i\) 和制造成本 \(c_i\),\(k\) 件商品价格 \(a_j\),总预算 \(W\)。需要选择若干商品购买,使得制造对应面值假币的总成本不超过 \(W\),且商品总价格最大。这是一个完全背包 + 01背包的组合问题:第一阶段用完全背包计算制造每种面值的最小成本,第二阶段用01背包选择商品最大化总价格。
-
算法选择:
- 完全背包(第一阶段):计算制造总面值恰好为 \(j\) 的假币的最小花费 \(dp[j]\)
- 01背包(第二阶段):在预算 \(W\) 内选择商品,最大化总价格 \(dp2[j]\)
- 两阶段DP组合:第一阶段的结果作为第二阶段的成本输入
-
关键步骤:
-
Step 1 - 完全背包预处理:
- 状态定义:\(dp[j]\) 表示制造总面值恰好为 \(j\) 的假币的最小花费
- 初始化:
memset(dp, 0x3f, sizeof(dp)),\(dp[0] = 0\) - 状态转移:\(dp[j] = \min(dp[j], dp[j - m[i]] + c[i])\)(完全背包,从小到大遍历 \(j\))
- 结果:\(dp[a[i]]\) 即为制造面值 \(a[i]\) 假币的最小成本
-
Step 2 - 01背包选择商品:
- 状态定义:\(dp2[i][j]\) 表示考虑前 \(i\) 件商品,花费恰好为 \(j\) 时的最大总价格
- 不选第 \(i\) 件商品:\(dp2[i][j] = dp2[i-1][j]\)
- 选第 \(i\) 件商品:若 \(j \geq dp[a[i]]\)(预算足够),则 \(dp2[i][j] = \max(dp2[i][j], dp2[i-1][j - dp[a[i]]] + a[i])\)
- 注意:购买第 \(i\) 件商品的成本是 \(dp[a[i]]\)(制造对应面值假币的最小花费)
-
Step 3 - 统计答案:
- 答案为 \(dp2[k][W]\),即在预算 \(W\) 内购买商品的最大总价格
-
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times M + k \times W)\),其中 \(M\) 为完全背包的上限(最大商品价格)
- 空间复杂度:\(O(M + k \times W)\),完全背包数组 \(dp[M]\) 和01背包数组 \(dp2[k][W]\)
-
完全背包 + 01背包组合的核心思想:
- 问题分解:将原问题分解为两个子问题——"制造假币的最小成本"和"在预算内选择商品"
- 第一阶段(完全背包):预处理出所有可能面值的最小制造成本,为第二阶段提供成本数据
- 第二阶段(01背包):将每件商品视为01背包中的物品,成本为第一阶段的计算结果,价值为商品价格
- 状态衔接:第一阶段的 \(dp[a[i]]\) 直接作为第二阶段的状态转移参数
- 最优子结构:两个阶段都满足最优子结构性质,组合后得到全局最优解
- 适用于多阶段决策、成本预处理 + 选择优化的组合问题
【算法标签】
完全背包
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// ================= 常量与全局变量 =================
const int N = 105; // 最大面值种类数 / 最大商品数
const int M = 100005; // 最大面值总额(用于完全背包)
int n; // 面值种类数
int k; // 商品数量
int W; // 总预算(造币花费上限)
int m[N]; // 每种面值的面额
int c[N]; // 制造一张该面值假币的花费
int a[N]; // 每件商品的价格
int w[N]; // 未使用的数组(保留原样)
int v[N]; // 未使用的数组(保留原样)
int dp[N][M]; // 完全背包 dp 数组
// dp[i][j] 表示考虑前 i 种面值,制造总面值恰好为 j 的最小花费、
int dp2[N][M]; // 01背包 dp 数组
// dp2[i][j] 表示考虑前 i 件商品,花费恰好为 j 时的最大总价格
// ================= 主函数 =================
int main()
{
// 读取面值种类数、商品数量和总预算
cin >> n >> k >> W;
// 读取每种面值的面额和制造成本
for (int i = 1; i <= n; i++)
cin >> m[i] >> c[i];
// 读取每件商品的价格
for (int i = 1; i <= k; i++)
cin >> a[i];
// ========= 第一阶段:完全背包 =========
// 计算制造一定面值假币的最小花费
// 初始化 dp 数组为无穷大
memset(dp, 0x3f, sizeof(dp));
dp[0][0] = 0; // 制造 0 面值花费为 0
// 完全背包:每种面值的假币可以无限制造
for (int i = 1; i <= n; i++)
for (int j = 0; j <= 100000; j++)
{
dp[i][j] = dp[i - 1][j]; // 不使用第 i 种面值
if (j >= m[i]) // 使用第 i 种面值
dp[i][j] = min(dp[i][j], dp[i][j - m[i]] + c[i]);
}
// ========= 第二阶段:01背包 =========
// 选择商品,最大化总价格
// dp2[i][j] 表示考虑前 i 件商品,花费恰好为 j 时的最大总价格
for (int i = 1; i <= k; i++)
{
for (int j = 0; j <= W; j++)
{
dp2[i][j] = dp2[i - 1][j]; // 不买第 i 件商品
int cost = dp[n][a[i]]; // 制造价格为 a[i] 的假币所需的最小花费
if (j >= cost) // 如果预算足够
dp2[i][j] = max(dp2[i][j], dp2[i - 1][j - cost] + a[i]); // 购买第 i 件商品
}
}
// 输出在预算 W 内能买到的最大总价格
cout << dp2[k][W] << endl;
return 0;
}
【运行结果】
5 5 30
10 7
7 2
6 3
9 5
1 9
11 2 32 22 27
59
浙公网安备 33010602011771号