复赛一:货币系统(money)
洛谷:p5020
OJ:i5020
算法思路:
题目要求找到一个与原货币系统等价的最小货币系统,使得货币种数 ( m ) 尽可能小。
等价的货币系统定义:
两个货币系统等价,当且仅当对于任意非负整数金额 ( x ),要么都能被两个系统表示,要么都不能被表示。
问题转化:
我们需要从原有的货币面额中选出尽可能少的面额,使得在原货币系统中能够表示的所有金额,在新货币系统中仍然能够表示;反之,在原系统中不能表示的金额,在新系统中也不能表示。
算法步骤:
- 输入与预处理:
- 读取货币种数 ( n ) 和面额数组 ( a )。
- 对面额数组进行排序,方便后续处理。
- 初始化:
- 使用一个布尔数组 ( dp ) 来记录哪些金额是可达的。初始化 dp[0] = true(金额0可达)。
- 贪心选择必要的面额:
- 初始化最小货币种数 ( m = 0 )。
- 遍历排序后的面额数组,对于每个面额 c = a[i] :
- 检查当前面额是否是必要的:
- 如果 dp[c] = true,说明当前面额c可以由之前选取的面额组合而成,是多余的,不需要加入新的货币系统。
- 如果 dp[c] = false,说明当前面额 c 无法由之前的面额组合而成,是必要的,需要加入新的货币系统。
- 更新状态:
- 如果当前面额是必要的,增加货币种数 m 。
- 使用当前面额 c 更新 dp 数组,使得新的金额可达:
[
dp[j] = dp[j] ∨ dp[j – c], 对于所有 j ≥ c
]
- 这个过程实际上是在检查每个面额是否是必要的,如果它无法由之前的面额组合表示,那么它就是必要的。
- 输出结果:
- 输出最小的货币种数 m 。
算法核心思想:
- 贪心策略: 我们希望选择尽可能少的面额来覆盖原系统中的所有可达金额。
- 可达性判定: 使用动态规划的方法,记录哪些金额是可达的。对于每个面额,判断它是否可以由之前的面额组合而成。
- 必要性判定: 如果一个面额无法由之前的面额组合而成,那么它就是必要的,必须加入新的货币系统。
举例说明:
以输入样例1为例:
4
3 6 10 19
算法步骤:
- 面额排序:将面额数组按从小到大排序,得到:a=[3,6,10,19]
- 初始化:
- 最大面额 max_coin=19
- 初始化动态规划数组 dp[0..19]为 false,但 dp[0]=true,表示金额0可达。
- 设定必要的货币种数 m=0
- 遍历面额数组,判断每个面额是否必要:
(1)面额 c=3:
检查是否必要:
dp[3]=false,因此面额3是必要的。更新必要的货币种数 m=m+1=1
更新 dp 数组:
对于 j从 c到 max_coin,即 j=3 到 19:dp[j]=dp[j]∨dp[j−c]
更新过程:
dp[3]=dp[3]∨dp[0]=false∨true=true
dp[6]=dp[6]∨dp[3]=false∨true=true
dp[9]=dp[9]∨dp[6]=false∨true=true
以此类推,更新 dp[12],dp[15],dp[18] 为 true
(2)面额 c=6:- 检查是否必要:
- dp[6]=true,说明金额6已可达,面额6是不必要的。跳过,不更新 m和 dp数组。
检查是否必要:
dp[10]=false,因此面额10是必要的。更新必要的货币种数 m=m+1=2
更新 dp 数组:
对于 j=10 到 19:
dp[10]=dp[10]∨dp[0]=false∨true=true
dp[13]=dp[13]∨dp[3]=false∨true=true
dp[16]=dp[16]∨dp[6]=false∨true=true
dp[19]=dp[19]∨dp[9]=false∨true=true
(4)面额 c=19c = 19c=19:- 检查是否必要:
- dp[19]=true,说明金额19已可达,面额19是不必要的。跳过,不更新 m 和 dp 数组。
- 检查是否必要:
- 最终结果:
- 必要的面额有 3 和 10,最小的货币种数 m=2
总结:
该算法通过动态规划和贪心策略,确定了哪些面额是必要的,从而找到与原货币系统等价且货币种数最少的系统。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | /**************************************************************** * Description: 2018年提高组第一题 货币系统 * Author: Alex Li * Date: 2024-09-30 19:28:38 * LastEditTime: 2024-09-30 19:38:55 ****************************************************************/ #include <iostream> #include <algorithm> #include <vector> #include <cstring> using namespace std; const int MAX_COIN_VALUE = 250; // 最大面值 int main() { int T; // 测试数据组数 cin >> T; while (T--) { int n; // 货币种数 cin >> n; vector<int> a(n); // 面额数组 int max_coin = 0; // 最大面值 for (int i = 0; i < n; ++i) { cin >> a[i]; max_coin = max(max_coin, a[i]); } sort(a.begin(), a.end()); // 将面额排序 vector<bool> dp(max_coin + 1, false); // dp数组,表示金额是否可达 dp[0] = true; // 金额0可达 int m = 0; // 最小货币种数 for (int i = 0; i < n; ++i) { int c = a[i]; // 当前面额 if (dp[c]) { // 如果当前面额可达,则是多余的 continue; } else { // 否则,是必要的,更新dp数组 m++; // 增加必要的货币种数 for (int j = c; j <= max_coin; ++j) { dp[j] = dp[j] || dp[j - c]; } } } cout << m << endl; // 输出最小的m } return 0; } |
