【NOIP 2018 提高组】 货币系统 —— 贪心 + DP

NOIP 2018 提高组 货币系统

题目背景:NOIP 2018 提高组 D1 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] \times 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 T T 组数据。 每组数据的第一行包含一个正整数 n n n。接下来一行包含 n n n 个由空格隔开的正整数 a [ i ] a[i] a[i]

输出格式

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


样例 1

样例输入
2 
4 
3 19 10 6 
5 
11 29 13 19 17
样例输出
2   
5

提示

在第一组数据中,货币系统 ( 2 , [ 3 , 10 ] ) (2, [3,10]) (2,[3,10]) 和给出的货币系统 ( n , a ) (n, a) (n,a) 等价,并可以验证不存在 m < 2 m \lt 2 m<2 的等价的货币系统,因此答案为 2 2 2。 在第二组数据中,可以验证不存在 m < n m < n m<n 的等价的货币系统,因此答案为 5 5 5


【数据范围与约定】

对于 100 % 100\% 100% 的数据,满足 1 ⩽ T ⩽ 20 1 \leqslant T \leqslant 20 1T20 n ,   a [ i ] ⩾ 1 n, \space a[i] \geqslant 1 n, a[i]1


算法:贪心 + DP

  1. 首先预置组成的意思 —— 对 ∀ x ∈ N \forall x \in N xN,从一个集合 S S S 里选择一些 ⩽ x \leqslant x x 的数 ( s 1 ,   s 2 ,   . . . ,   s k ) (s_1, \space s_2, \space ..., \space s_k) (s1, s2, ..., sk) ,有 x = ∑ i = 1 k t i × s i x = \sum\limits_{i = 1}^k{t_i \times s_i} x=i=1kti×si t i > 0 t_i \gt 0 ti>0)。

  2. 初步思路:把 ( n ,   a ) (n, \space a) (n, a) ( m ,   b ) (m, \space b) (m, b) 看作集合 A A A B B B,根据题目意思,就是对任意 x x x,都要有 A A A B B B 能够组成 x x x,否则两个集合都不能组成 x x x;由于题目要求 B B B 最小,所以可以猜测 B B B A A A 的子集

  3. 先证一个前置结论: x ∈ A x \in A xA,且 x x x 无法由 A A A ∣ | x x x(即 A A A x x x 的补集,下用 x c x^c xc 表示)组成,则 x ∈ B x \in B xB。下用反证法,不妨设存在满足上述条件的 x x x,有 x ∉ B x \notin B x/B,根据题目条件, B B B 中没有 x x x,意味着必须有 ( b 1 ,   b 2 ,   . . . b p ) (b_1, \space b_2, \space ... b_p) (b1, b2, ...bp)   b i ∈ B \space b_i \in B  biB,组成 x x x,那么这些元素就至少存在一个 b t ∉ A b_t \notin A bt/A b t b_t bt 不能由 A A A 组成,否则 所有 b i ∈ A b_i \in A biA,那说明 x x x 是可以由 x c x^c xc 组成的,或是 b t ∉ A b_t \notin A bt/A,但 b t b_t bt 能由 A A A 组成,即 b t = ∑ i = 1 k ′ t n i × a n i b_t = \sum\limits_{i = 1}^{k'}{t_{n_i} \times a_{n_i}} bt=i=1ktni×ani 1 ⩽ n i ,   k ′ ⩽ k 1 \leqslant n_i, \space k' \leqslant k 1ni, kk),所以可以将 b t b_t bt 拆成 A A A 中元素,同理对其他满足这个性质的 b i b_i bi 都拆成 A A A 中元素,再加上这些元素一定都比 x x x 小,这样就推导出 x c x^c xc 能够组成 x x x,与假设矛盾,所以结论成立。


贪心部分
  1. 首先需要证明: B ⊂ A B \subset A BA。根据子集的定义, ∀ x ∈ B \forall x \in B xB,都有 x ∈ A x \in A xA,于是我们用反证法,不妨设 ∃ x ∈ B \exists x \in B xB,有 x ∉ A x \notin A x/A。根据题目意思, A A A 中没有 x x x,那就必须有 ( a 1 ,   a 2 ,   . . . a p ) (a_1, \space a_2, \space ... a_p) (a1, a2, ...ap)   a i ∈ A \space a_i \in A  aiA,组成 x x x,若其中有一些 a i a_i ai 可以被 a i c a_i^c aic 组成,那么就将其替换为组成 a i a_i ai a i c a_i^c aic 中的元素,这样就可以得到一个由 A A A 组成 x x x 的集合 ( a n 1 ,   a n 2 ,   . . . a n k ′ ) (a_{n_1}, \space a_{n_2}, \space ... a_{n_{k'}}) (an1, an2, ...ank),这个集合内的每一个元素 a n i a_{n_i} ani 都不能由 a n i c {a_{n_i}^c} anic 组成,于是由上述的前置结论,一定有 ∀ a n i ∈ B \forall a_{n_i} \in B aniB,所以就有 x c x^c xc 能够组成 x x x(这里是关于 B B B 的补集)。再接着推导,就能得到,所有含有 x x x 的集合,其所能组成的数,将 x x x 替换为 x c x^c xc 中组成 x x x 的集合的元素,也完全可以,因此整个集合 B B B 减去一个 x x x 同样满足题目要求,所以此时的 B B B 集合不是最小的,与假设矛盾,所以 ∀ x ∈ B \forall x \in B xB,都有 x ∈ A x \in A xA,即 B B B A A A 的子集成立。

  2. 接下来开始求解 B B B 集合。通过上面的推导,我们很容易得出一个猜想:对于 A A A 中所有不能由 a i c a_i^c aic 组成的 a i a_i ai,都应该有 a i ∈ B a_i \in B aiB。这个证明可以分成两边,一边是往扩大集合的角度,一边是往缩小集合的角度。第一是扩大集合,这个角度很显然地被排除了,因为上述推导 B ⊂ A B \subset A BA 的时候就提到了,如果一个数能被一个集合组成,那就把这个数换成组成它的集合的元素,对结果没有影响,所以 B B B 数组不需要再扩大了。第二是缩小集合的角度,若 B B B 数组在上述假设的基础上再扣掉一些 a i a_i ai,那么这些被扣掉的 a i a_i ai 就根本无法由任何 B B B 的子集组成,因为它们除了自己等于自己以外,无法由任何集合组成,所以 B B B 数组也无法缩小。由夹逼定理, B B B 集合就等于 A A A 中所有不能由 a i c a_i^c aic 组成的 a i a_i ai 的集合。


DP 部分
  1. 首先根据上述的贪心,我们可以将所有数从小到大排序,接着从前往后扫,因为小的数才有可能作为上述的备选对象,即无法由任何集合组成。

  2. f ( i , j ) f(i, j) f(i,j) 为从前 i i i 个数中选, f ( i , j ) = 1 f(i, j) = 1 f(i,j)=1 表示 j j j 能被表示出来, f ( i , j ) = 0 f(i, j) = 0 f(i,j)=0 表示 j j j 无法被表示。

  3. 于是有状态转移方程 f ( i , j ) = f ( i , j )   ∣   f ( i − 1 , j − a [ i ] ) f(i, j) = f(i, j) \space | \space f(i - 1, j - a[i]) f(i,j)=f(i,j)  f(i1,ja[i])

  4. 注意这里要优化掉第一维的话需要像完全背包那样优化,因为每个数都可以无限选。

参考文献:第一篇题解

时间复杂度 O ( T × n × m ) O(T \times n \times m) O(T×n×m) m = max ⁡ 1 ⩽ i ⩽ n a [ i ] m = \max\limits_{1 \leqslant i \leqslant n}{a[i]} m=1inmaxa[i]
C ++ 代码
#include <iostream>
#include <algorithm>
#include <cstring>

#define x first
#define y second

#define endl '\n'

#define fup(i, a, b) for (int i = a; i <= b; i ++ )
#define fdn(i, a, b) for (int i = a; i >= b; i -- )

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 110, M = 30010, MOD = 1e9 + 7;

int n, m;
int a[N], f[M];

void solve()
{
    cin >> n;
    fup(i, 1, n) cin >> a[i];
    sort(a + 1, a + n + 1);

    memset(f, 0, sizeof f);
    f[0] = 1;

    int res = 0;
    fup(i, 1, n) 
    {
        if (!f[a[i]]) res ++ ;
        fup(j, a[i], a[n]) f[j] |= f[j - a[i]];
    }

    cout << res << endl;
}

int main()
{
    int T = 1;
    cin >> T;
    while (T -- ) solve();

    return 0;
}
### 货币系统中的位集实现 在处理货币系统的编程场景下,位集(bitset)可以用于优化存储空间以及提高某些操作的速度。对于货币系统而言,通常涉及大量交易记录、账户状态以及其他金融数据的管理。 #### 使用位集表示账户状态 通过使用位集来追踪不同类型的账户状态或权限级别能够显著减少内存占用并加快查询速度。例如,在一个多租户银行应用程序中,每位客户可能拥有多种服务订阅情况: - 是否开通国际转账功能 - 是否启用在线支付选项 - 是否激活了定期存款计划 这些布尔属性可以通过单个整数内的各个二进制位来进行编码[^1]。 ```cpp #include <iostream> #include <bitset> int main() { std::bitset<8> account_features(0b0000'0000); // 初始化为空白特征集合 // 假设最低有效位代表是否开启国际转账, 接下来一位是在线支付... account_features.set(0); // 开启国际转账 account_features.set(2); // 启用定期存款 if (account_features.test(0)) // 检查是否有权进行国际转账 std::cout << "允许国际转账\n"; return 0; } ``` 此方法不仅节省了数据库表结构设计上的复杂度,而且使得客户端逻辑更加简洁高效。 然而值得注意的是,在实际开发过程中也存在一些挑战和潜在问题: - **可读性和维护性**:随着业务需求的增长,可能会增加更多种类的状态标志位,这可能导致代码难以理解和维护。 - **并发控制**:当多个线程试图同时修改同一个用户的bit字段时,如果没有适当同步机制,则可能发生竞争条件。 - **移植性考虑**:不同的硬件平台对字节序有不同的规定,因此如果程序需要跨平台运行的话,应该小心处理端序转换等问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值