NOIP2018提高组复赛T2:货币系统

题目描述

在网友的国度中共有  n n n 种不同面额的货币,第  i i i 种货币的面额为  a [ i ] a[i] a[i],你可以假设每一种货币都有无穷多张。

为了方便,我们把货币种数为  n n n、面额数组为  a [ 1.. n ] a[1..n] a[1..n] 的货币系统记作  ( n , a ) (n,a) (n,a)

在一个完善的货币系统中,每一个非负整数的金额  x x x 都应该可以被表示出,即对每一个非负整数  x x x,都存在  n n n 个非负整数  t [ i ] t[i] t[i] 满足  a [ i ] × t [ i ] a[i]×t[i] a[i]×t[i] 的和为  x x x

然而,在网友的国度中,货币系统可能是不完善的,即可能存在金额  x x x 不能被该货币系统表示出。

例如在货币系统  n = 3 n=3 n=3,  a = [ 2 , 5 , 9 ] a=[2,5,9] a=[2,5,9] 中,金额  1 , 3 1,3 1,3 就无法被表示出来。

两个货币系统  ( n , a ) (n,a) (n,a) 和  ( m , b ) (m,b) (m,b) 是等价的,当且仅当对于任意非负整数  x x x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。

现在网友们打算简化一下货币系统。

他们希望找到一个货币系统  ( m , b ) (m,b) (m,b),满足  ( m , b ) (m,b) (m,b) 与原来的货币系统  ( n , a ) (n,a) (n,a) 等价,且  m m m 尽可能的小。

他们希望你来协助完成这个艰巨的任务:找到最小的  m m m

输入格式
输入文件的第一行包含一个整数  T T T,表示数据的组数。

接下来按照如下格式分别给出T组数据。

每组数据的第一行包含一个正整数  n n n

接下来一行包含  n n n 个由空格隔开的正整数  a [ i ] a[i] a[i]

输出格式
输出文件共有T行,对于每组数据,输出一行一个正整数,表示所有与  ( n , a ) (n,a) (n,a) 等价的货币系统  ( m , b ) (m,b) (m,b) 中,最小的  m m m

数据范围
1 ≤ n ≤ 100 1≤n≤100 1n100,
1 ≤ a [ i ] ≤ 25000 1≤a[i]≤25000 1a[i]25000,
1 ≤ T ≤ 20 1≤T≤20 1T20

输入样例:

2
4
3 19 10 6
5
11 29 13 19 17

输出样例:

2
5

算法思想(DP、完全背包求方案数)

根据通过题目的描述,分析测试样例:

  • 第一组, n = 4 n=4 n=4,  a = [ 3 , 19 , 10 , 6 ] a=[3,19,10,6] a=[3,19,10,6],与之等价的货币系统  m = 2 m=2 m=2,  b = [ 3 , 10 ] b=[3,10] b=[3,10]
  • 第二组, n = 5 n=5 n=5,  a = [ 11 , 29 , 13 , 19 , 17 ] a=[11,29,13,19,17] a=[11,29,13,19,17],与之等价的货币系统  m = 5 m=5 m=5,  b = [ 11 , 29 , 13 , 19 , 17 ] b=[11,29,13,19,17] b=[11,29,13,19,17]

模拟测试样例可以发现如下性质(这里假设 { b 1 , b 2 , b 3 , . . . , b m } \{{b_1, b_2, b_3,..., b_m \}} {b1,b2,b3,...,bm}为最优货币系统 ( m , b ) (m,b) (m,b)中的货币面额):

  1. 货币系统 ( n , a ) (n, a) (n,a)中, a i ∈ { a 1 , a 2 , a 3 , . . . , a n } a_i ∈ \{{a_1, a_2, a_3,..., a_n \}} ai{a1,a2,a3,...,an} 一定可以被最优解中的 b i b_i bi表示出来
  2. 最优解 ( m , b ) (m, b) (m,b)中, b 1 , b 2 , . . . . , b m b_1,b_2, ...., b_m b1,b2,....,bm 一定不能被其它 b i b_i bi表示出来
  3. 最优解 ( m , b ) (m, b) (m,b)中, b 1 , b 2 , . . . . , b m b_1,b_2, ...., b_m b1,b2,....,bm 一定都是从 { a 1 , a 2 , a 3 , . . . , a n } \{{a_1, a_2, a_3,..., a_n \}} {a1,a2,a3,...,an} 中选择出来的

性质1、2显而易见。下面使用反证法证明性质3:

  • 假设最优解 ( m , b ) (m, b) (m,b)中存在 b i ∉ { a 1 , a 2 , a 3 , . . . , a n } b_i∉\{{a_1, a_2, a_3,..., a_n \}} bi/{a1,a2,a3,...,an},因为两个货币系统是等价的,当且仅当对于任意非负整数  x x x,它要么均可以被两个货币系统表出,即最优解中的 b i b_i bi可以由货币系统 ( n , a ) (n, a) (n,a)中若干面额的货币表出,例如: b i = t 1 × a j + t 2 × a k + . . . b_i=t_1\times a_j + t_2 \times a_k + ... bi=t1×aj+t2×ak+...
  • 而货币系统 ( n , a ) (n, a) (n,a) a j , a k , . . a_j,a_k,.. aj,ak,..均可以由货币系统 ( m , b ) (m, b) (m,b)面额小于 b i b_i bi的货币表出,即可以得到 b i = r 1 × b j + r 2 × b k . . . b_i=r_1\times b_j + r_2\times b_k... bi=r1×bj+r2×bk...
  • 这与假设最优解 ( m , b ) (m, b) (m,b)是最优解矛盾,证毕。

得到性质1、2、3后,本题就变成了从 { a 1 , a 2 , a 3 , . . . , a n } \{{a_1, a_2, a_3,..., a_n \}} {a1,a2,a3,...,an} 中选择出不能被其它面额表示出来的货币个数。可以使用完全背包问题的思想,求装满背包的方案数。如果方案数为0,则不能表示出来;否则,可以表示出来。

注意,为了方便计算,需要先将面额 a i a_i ai从小到大排序,用面额小的来组成面额大的货币。

背包问题求解方案数,可以参考博主的另一篇文章——每周一算法:背包问题(五)求解方案数

状态表示

f [ j ] f[j] f[j]表示组成面值为 j j j的方案总数。

状态计算

f [ j ] = f [ j ] + f [ j − v [ i ] ] f[j] = f[j] + f[j-v[i]] f[j]=f[j]+f[jv[i]]

初始状态

f [ 0 ] = 1 f[0]=1 f[0]=1,表示组成面值为0的方案总数为 1 1 1

时间复杂度

经过完全背包二维变一维的优化后,时间复杂度为 O ( n ∗ a [ n ] ) O(n * a[n]) O(na[n])

代码实现

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110, M = 25010;
int a[N], f[M];
int main()
{
    int T;
    cin >> T;
    while(T --)
    {
        int n;
        cin >> n;
        for(int i = 1; i <= n; i ++) cin >> a[i];        
        sort(a + 1, a + n + 1);
        memset(f, 0, sizeof f);
        f[0] = 1; //初始状态        
        int m = a[n]; //背包容量
        int res = 0;
        for(int i = 1; i <= n; i ++)
        {
            if(f[a[i]] == 0) res ++;//如果当前面额不能被其它面额表示
            //完全背包优化,从小到大枚举背包容量   
            for(int j = a[i]; j <= m; j ++)
                f[j] = f[j] + f[j - a[i]];
        }        
        cout << res << endl;
    }    
    return 0;
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少儿编程乔老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值