Goose Coins
全是观察.
题意描述
鹅币王国使用 \(n\) 种鹅币作为国家货币。第 \(i\) 种鹅币的价值为 \(c_i\) 鹅元,重量为 \(w_i\)。对于所有的 \(i\ (1 \le i \le n-1)\),都满足 \(c_{i+1}\) 是 \(c_i\) 的倍数,且 \(c_i < c_{i+1}\)。
你在鹅市场购买了价值 \(p\) 鹅元的商品,希望使用恰好 \(k\) 枚鹅币来精确支付。每种类型的鹅币你都有无限多枚,因此无需担心硬币不足的问题。
请编写一个程序,找出满足条件的 \(k\) 枚硬币的最小和最大可能总重量。如果不存在这样的硬币组合,则输出 \(-1\)。
思路
观察到如果 \(p \not\equiv 0 \pmod {c_1}\), 那么直接输出 \(-1\) 即可.
题目要求我们使用恰好 \(k\) 个鹅币组成价值 \(p\), 倘若我们先不考虑重量, 怎么使得使用的鹅币最少.
显然, 从小到大, 对于每一个鹅币 \(i\) 我们取 \(\displaystyle \lfloor \frac{p}{c_i} \rfloor\) 个, 然后 \(p := p \bmod c_i\), 这样取出来的鹅币数量一定是最少的.
将每个鹅币取得数量称为「系数」. 例如对于 \(p = 20\), 序列 \(c = [1, 2, 6]\), 那么系数序列 \(t = [0, 1, 3]\). 因为题目中给出 \(c_{i + 1}\) 为 \(c_i\) 的倍数, 所以系数是可以下放的. 具体来说, 对于 \(1 \le j < i \le n\), 如果我们少取 \(k\) 个 \(c_i\), 那么就可以多取 \(\displaystyle \frac{c_i}{c_j} \cdot k\) 个 \(c_j\) 来补足.
现在考虑重量, 我们可以列出 \(\rm{DP}\) 方程
- \(f_{i, j, k}\) 表示从后往前枚举到第 \(i\) 种鹅币, 还需要 \(j\) 个鹅币, 当前还需要 \(k\) 个第 \(i\) 种鹅币补足前面下放的最小 / 最大代价.
不选第 \(i\) 种鹅币.
其中 \(\displaystyle x = \frac{c_{i + 1}}{c_i} \cdot k +t_i\), \(t\) 即为前面所述的系数序列.
选择第 \(i\) 种鹅币, 类似于背包
时间复杂度 \(\mathcal{O}(n k^2)\).
#include <iostream>
#include <cstring>using namespace std;#define int long longconstexpr int N = 62, M = 1001;int n, k, p, c[N], w[N], x[N], f[N][M][M], g[N][M][M];void init() {memset(f, 63, sizeof f);memset(g, 128, sizeof g);cin >> n >> k >> p;f[n + 1][k][0] = g[n + 1][k][0] = 0;for (int i = 1; i <= n; ++i) {cin >> c[i] >> w[i];}for (int i = n, t = p; i; t %= c[i--]) {x[i] = t / c[i];}
}void calculate() {if (p % c[1]) {puts("-1");return;}for (int i = n; i; --i) {for (int j = 0; j <= k; ++j) {for (int l = 0; l <= j; ++l) {int t = c[i + 1] / c[i] * l + x[i];if (t <= j) {f[i][j][t] = min(f[i][j][t], f[i + 1][j][l]);g[i][j][t] = max(g[i][j][t], g[i + 1][j][l]);}}}for (int j = k; j; --j) {for (int l = j; l; --l) {f[i][j - 1][l - 1] = min(f[i][j - 1][l - 1], f[i][j][l] + w[i]);g[i][j - 1][l - 1] = max(g[i][j - 1][l - 1], g[i][j][l] + w[i]);}}}if (g[1][0][0] < 0) {puts("-1");return;}cout << f[1][0][0] << ' ' << g[1][0][0] << '\n';
}void solve() {init();calculate();
}signed main() {solve();return 0;
}