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 1⩽T⩽20, n , a [ i ] ⩾ 1 n, \space a[i] \geqslant 1 n, a[i]⩾1。
算法:贪心 + DP
-
首先预置组成的意思 —— 对 ∀ x ∈ N \forall x \in N ∀x∈N,从一个集合 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=1∑kti×si( t i > 0 t_i \gt 0 ti>0)。
-
初步思路:把 ( 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 的子集。
-
先证一个前置结论:若 x ∈ A x \in A x∈A,且 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 x∈B。下用反证法,不妨设存在满足上述条件的 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 bi∈B,组成 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 bi∈A,那说明 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=1∑k′tni×ani( 1 ⩽ n i , k ′ ⩽ k 1 \leqslant n_i, \space k' \leqslant k 1⩽ni, k′⩽k),所以可以将 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,与假设矛盾,所以结论成立。
贪心部分
-
首先需要证明: B ⊂ A B \subset A B⊂A。根据子集的定义, ∀ x ∈ B \forall x \in B ∀x∈B,都有 x ∈ A x \in A x∈A,于是我们用反证法,不妨设 ∃ x ∈ B \exists x \in B ∃x∈B,有 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 ai∈A,组成 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 ∀ani∈B,所以就有 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 ∀x∈B,都有 x ∈ A x \in A x∈A,即 B B B 是 A A A 的子集成立。
-
接下来开始求解 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 ai∈B。这个证明可以分成两边,一边是往扩大集合的角度,一边是往缩小集合的角度。第一是扩大集合,这个角度很显然地被排除了,因为上述推导 B ⊂ A B \subset A B⊂A 的时候就提到了,如果一个数能被一个集合组成,那就把这个数换成组成它的集合的元素,对结果没有影响,所以 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 部分
-
首先根据上述的贪心,我们可以将所有数从小到大排序,接着从前往后扫,因为小的数才有可能作为上述的备选对象,即无法由任何集合组成。
-
设 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 无法被表示。
-
于是有状态转移方程 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(i−1,j−a[i])。
-
注意这里要优化掉第一维的话需要像完全背包那样优化,因为每个数都可以无限选。
参考文献:第一篇题解
时间复杂度 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=1⩽i⩽nmaxa[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;
}