diff --git a/TangDou/AcWing/BeiBao/背包问题专题.md b/TangDou/AcWing/BeiBao/背包问题专题.md index 4f77ef3..ffc8e0b 100644 --- a/TangDou/AcWing/BeiBao/背包问题专题.md +++ b/TangDou/AcWing/BeiBao/背包问题专题.md @@ -305,4 +305,63 @@ int main() { printf("%lld\n", f[m]); return 0; } -``` \ No newline at end of file +``` + +**[$AcWing$ $532$. 货币系统](https://www.acwing.com/problem/content/description/534/)** + +**总结** +非常经典的$NOIP$题目!一般都是需要自己挖掘一些性质,然后再用代码模板去解题,挖掘的性质一般靠经验去猜。 + +常见的套路包括:排序,按数对左端点排序啥的。 + +排序完成后,逐个遍历一下,看看这个数字能不能用它前面的数字表示出来(完全背包+恰好装满),如果能的话,说明这个数字可以扔掉,因为扔掉后依然可以靠比它小的构造出来,如果不能就必须保留下来,最终统计一下数字个数就行了。 + +本题最后一个细节:不能跑多次$DP$,性能差,需要只跑一次$DP$,可以使用求组成方案数,如果只有一种方案,即$f[i]=1$表示$i$只能用自己表示自己,就是需要保留的。 + +```cpp {.line-numbers} +#include + +using namespace std; +const int N = 110; // N个正整数 +const int M = 25010; // 表示的最大金额上限 +int n; // 实际输入的正整数个数 +int v[N]; // 每个输入的数字,也相当于占用的体积是多大 +int f[M]; // 二维优化为一维的DP数组,f[i]:面额为i时的前序货币组成方案数 + +int main() { + int T; + cin >> T; + while (T--) { + // 每轮初始化一次dp数组,因为有多轮 + memset(f, 0, sizeof f); + + cin >> n; + for (int i = 0; i < n; i++) cin >> v[i]; + // 每个货币的金额,都只能由比它小的货币组装而成,需要排一下序 + sort(v, v + n); + + // 背包容量 + int m = v[n - 1]; + + // 在总金额是0的情况下,只有一种方案 + f[0] = 1; + + // 恰好装满:计算每个体积(面额)的组成方案 + for (int i = 0; i < n; i++) + for (int j = v[i]; j <= m; j++) + f[j] += f[j - v[i]]; + + // 统计结果数 + int res = 0; + for (int i = 0; i < n; i++) + // 如果当前面额的组成方案只有一种,那么它只能被用自己描述自己,不能让其它人描述自己 + // 这个面额就必须保留 + if (f[v[i]] == 1) res++; + // 输出结果 + printf("%d\n", res); + } + return 0; +} + +``` +