[Leaf] [P5020] 货币系统

luogu-p5020-货币系统">Luogu-P5020 货币系统

1. 题面梗概

中译中: 给你一个硬币集合 AA,你要找一个最小的集合 BB,使得 BB 能凑出来的钱和 AA 能凑出来的钱一模一样。求 BB 里面最少有几个数。

2. 逻辑推导

看到这个题,第一反应是:感觉像是线性代数中的求基,但在数论背景下,我们需要深入分析。

直觉:我猜测我们要找的 BB 集合应该是 AA 集合的一个子集,但直觉归直觉,我们需要通过逻辑证明:

2.1 反证法

  1. 我们先证明:AA 集合中不能被其他元素组成的数,必然存在于 BB 集合中。

    • 设 x∈Ax \in A 且 xx 不能被 AA 集合外除它以外的元素组成。若 x∉Bx \notin B,那么就说明 BB 集合中必然有一组数能够组成 xx。

    • 那么这组数中至少存在一个不在 AA 集合内,且无法被 AA 集合中组成的数,矛盾。

  2. 接着就可以证明 B⊆AB \subseteq A。

    • 设 x∈Bx \in B 且 x∉Ax \notin A。由于 BB 和 AA 等价,那么 xx 必然能被 AA 中的数字组成。而这些数字又属于 BB。
    • 这意味着 xx 能被 BB 中的其他数组组成,那么 BB 去掉 xx 后依然等价,这与 BB 的定义矛盾。

结论:BB 就是从 AA 中剔除掉所有能被其他数组成的元素后剩下的集合。

2.2 解决

根据数学常识,一个数 xx 只能被比它小的数组成。那么很显然我们只要先将数组排个序,然后考虑每个数能不能被前面的数字组成即可。

写到这里就很明显了,我们可以用搜索,也可以用背包DP,这里我采用完全背包来解决。

DP:f[x]f[x] 表示 xx 这个数能否被组成。f[x]=f[x]∨f[x−ai]f[x] = f[x] \vee f[x-a_i]。

3. 代码实现

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
#include<bits/stdc++.h>
using namespace std;

const int maxdp = 25000 + 50;
const int maxn = 100 + 5;

int dp[maxdp];
int a[maxn];
int n;

int main( )
{
int T;
cin >> T;
while( T -- )
{
memset( dp, 0, sizeof( dp ) );
cin >> n;
int ans = n;

for( int i = 1; i <= n; ++ i )
{
cin >> a[i];
}
sort( a + 1, a + n + 1 );

dp[0] = 1;
for( int i = 1; i <= n; ++ i )
{
if( dp[ a[i] ] )
{
ans --;
continue;
}
for( int j = a[i]; j <= a[n]; ++ j )
{
dp[j] = dp[j] | dp[ j - a[i] ];
}
}

cout << ans << '\n';
}
return 0;
}

4. 复盘

  • 复杂度: O(T⋅N⋅max⁡(A))O( T \cdot N \cdot \max(A) )。

  • 碎碎念: 这题的价值在于理解集合的关系,然后才是完全背包DP解决最后一步。

  • 关联笔记: 背包DP


[Leaf] [P5020] 货币系统
http://miqiu.space/posts/910566d1/
作者
Miqiu
发布于
2025年11月17日
许可协议