货币系统-计数类背包问题
问题
思路
题意:
对于给定长度为\(n\)的序列\(a\), 对于序列\(a\)种任意数的线性组合有
\(s = k_1a_1 + k_2a_2 + ..., \space k_i \in Z^+\)
求另一个序列满足\(a\)中能用线性组合表达的数都能在\(b\)中表示,求满足该条件的\(b\)序列的最小长度
性质:
- 对于任意的\(a_i\)一定都能够被序列\(b\) 序列本身表达
- 对于任意的\(b_i\)都不能被该序列的其他元素线性组合表达 (最优性)
有上述性质,可以推导对于任意的\(b_i \in a\)
证明:
假设\(b_i \notin a\), 则\(b_i = xa_y + ... + ya_z = lb_k + ... + mb_c\) (第二个等式是因为对于任意的\(a_i\)都能被\(b\)序列表示)成立,并且保证\(i\)与两个等式中的下标均不相等, 即该数可以被其他数线性表出,这与最优性不符,所以\(b_i \notin a\) 不成立
解题思路:
由上述性质,该问题转变为筛取序列\(a\)中能被线性表达的数后的,序列大小。
可以考虑完全背包计数,因为每个数可以选定无限次,而对于任意的\(a_i\), 只能被\(k \in a, k \le a_i\) 线性表达,我们将序列\(a\)排序,通过完全背包计数可以解决该问题。
\(f[i][j]\) 表示考虑前\(i\)个数,当背包容量为\(j\)时的方案数,其中\(0 \le j \le max(a)\)
Code
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using i64 = long long;
const int N = 2e4 + 5e3 + 10;
int dp[N];
void solve() {
memset(dp, 0, sizeof dp);
int n, m = -1, ans = 0;
std::cin >> n;
std::vector<int> a(n + 1);
for(int i = 1; i <= n; i ++) {
std::cin >> a[i];
m = std::max(m, a[i]);
}
std::sort(a.begin(), a.end());
dp[0] = 1;
for(int i = 1; i <= n; i ++) {
if(!dp[a[i]]) ans ++;
for(int j = a[i]; j <= m; j ++) {
dp[j] = dp[j] + dp[j - a[i]];
}
}
std::cout << ans << "\n";
}
int main() {
int _;
std::cin >> _;
while(_ --) {
solve();
}
}