CF687C 硬币面额
1 CF687C 硬币面额
2 题目描述
时间限制 \(2s\) | 空间限制 \(256M\)
\(Pari\) 想从 \(Arya\) 那里购买一种昂贵的巧克力。她有 \(n\) 个硬币,第 \(i\) 个硬币的价值是 \(c_i\)。巧克力的价格为 \(k\) 元,因此 \(Pari\) 会给 \(Arya\) 支付总共 \(k\) 元价格的硬币。看着自己的硬币,\(Pari\) 想知道把硬币给了 \(Arya\) 后,\(Arya\) 能用这些硬币拼凑出多少不同的面额呢?
3 题解
我们观察数据范围,发现 \(n, k \le 500\),时间复杂度应该大致为 \(O(n^3)\) 数量级。我们再仔细观察题目要求:答案应该是拼出 \(k\) 的和的货币能拼出的所有面额。我们此时如果只考虑二维状态 \(dp_{i, j}\) 表示前 \(i\) 枚货币中选取和为 \(j\) 的货币可以拼凑出的面额数,会发现我们无法确定出最终答案,且转移比较困难。为了解决这个问题,我们可以增加一维,令 \(dp_{i, j, k}\) 表示前 \(i\) 枚货币中选取和为 \(j\) 的货币能否拼出面额 \(k\)。由于题目中所给出的数据范围很小,这种状态的设计不会使空间超限。注意这里为了方便计算,我们令 \(dp_{i, j, k}\) 这一状态中默认选取了第 \(i\) 枚货币。因为我们后面总有一种情况并不选取第 \(i\) 枚货币,所以我们无需担心可能找不到最优的情况。
接下来我们来研究转移。一共有两种方案:第 \(i\) 枚货币和其他数一起拼出 \(k\),第 \(i\) 枚货币不用于拼凑。对于第一种情况我们的转移是:
这里我们用 \(|\) 是因为当 \(a | b\) 时,如果 \(a\) 和 \(b\) 其中有一个为 \(1\),答案就是 \(1\)。这种赋值方案使得我们可以在 \(dp_{i-1, j-c_i, k-c_i}\) 为 \(0\) 且 \(dp_{i, j, k}\) 为 \(1\) 时不让 \(0\) 影响答案,同时又在 \(dp_{i-1, j-c_i, k - c_i}\) 为 \(1\) 时更新 \(dp_{i, j, k}\)。回到这种转移,其实际意义为:如果存在一种方案,使前 \(i-1\) 枚货币中选取和为 \(j-c_i\) 的一些货币可以凑出 \(k-c_i\) 这一面额,那么直接将第 \(i\) 枚货币加上以上条件中的面额,必定可以在前 \(i\) 枚货币中选取和为 \(j\) 的若干货币拼凑出面额 \(k\)。
对于第二种情况,我们的转移是:
这个意义就相对来说更简单了,我们不用第 \(i\) 个数来拼凑出 \(k\),只需要看前面是否有和为 \(j - c_i\) 且能拼凑出 \(k\) 的情况。
这里我们发现:第一维其实与普通背包中的第一位差不多,可以采用滚动数组滚掉。这样 \(dp\) 的状态就变成二维的了,可以节省大量空间。输出时只需要输出
4 代码(空格警告):
#include <iostream>
using namespace std;
const int N = 505;
int n, k, ans;
int c[N], dp[N][N], s[N];
int main()
{
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> c[i];
dp[0][0] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = k; j >= c[i]; j--)
{
for (int l = k; l >= 0; l--)
{
if (l >= c[i]) dp[j][l] |= dp[j - c[i]][l - c[i]];
dp[j][l] |= dp[j-c[i]][l];
}
}
}
for (int i = 0; i <= k; i++) if (dp[k][i]) s[++ans] = i;
cout << ans << '\n';
for (int i = 1; i <= ans; i++) cout << s[i] << " ";
return 0;
}