[HAOI2008] 硬币购物
前言
手贱点进 \(\rm{TJ}\) , 还好啥都没看懂
再次想了一下考试应当怎么考, 并且与平时归起来了
其实焦虑是正常的, 做到自己的最好即可
加油!
思路
你发现这疑似多重背包
令 \(f_{i, j}\) 表示考虑了前 \(i\) 种硬币, 已经有了 \(j\) 元的可能性
考虑转移
\[f_{i, j} \gets f_{i - 1, j - k \cdot c_i}
\]
这个复杂度应该是 \(\mathcal{O} (ns)\) 的, 有些极限, 还有常数
怎么优化这个算法?
你发现又是这种本质相同的问题, 考虑全局的处理
全局处理显然是不考虑 \(d\) 的限制, 你直接处理全局的背包问题, 这是一个 \(\mathcal{O}(s)\) 的完全背包
你发现知道这个的基础上怎么根据 \(d\) 进行计数?
正难则反, 考虑总可能性减去违法的可能性, 压一下违法状态, 带进容斥柿子里计算 (其中 \(f(s)\) 是当前条件的情况下的方案数)
\[ans = \sum_{s = 0}^{2^4 - 1} (-1)^{\lvert s \rvert} f(s)
\]
如何计算 \(f(s)\) ?
考虑什么情况下超出了 \(i\) 的限制 : 选择的个数 \(> d_i\)
考虑怎样计算方案数:
你发现一旦选择了 \(d_i + 1\) 个当前硬币, 就一定会不满足 \(i\) 的条件, 余下的任选即可
所以我们利用之前的 \(f\) 数组就可以求出, 具体的, 假设不满足 \(i\) 的条件, 方案数为 \(f_{s - (d_i + 1) \times c_i}\) , 其他的直接计算同理
代码
#include <bits/stdc++.h>
#define ll long long
const int MAXN = 1e5 + 5;
ll f[MAXN], c[10], d[10], s;
inline long long read() {
char c = getchar();
long long sum = 0, f = 1;
while (c < '0' || c > '9') {
if (c == '-') f = -1;
c = getchar();
}
while (c >= '0' && c <= '9')
sum = ((sum << 1) + (sum << 3)) + (c - '0'), c = getchar();
return sum * f;
}
int main()
{
for (int i = 1; i <= 4; i++) c[i] = read();
int n = read();
f[0] = 1;
for (int i = 1; i <= 4; i++)
for (int j = c[i]; j <= 1e5; j++)
f[j] += f[j - c[i]];
while (n--) {
for (int i = 1; i <= 4; i++) d[i] = read();
s = read();
ll ans = f[s];
for (int i = 1; i <= 15; i++) {
ll k = 0, num = 0;
for (int j = 0; j < 4; j++) if (i & (1 << j)) num++, k += c[j + 1] * (d[j + 1] + 1);
ll fl = num % 2 ? -1 : 1;
if (s >= k) ans += fl * f[s - k];
}
printf("%lld\n", ans);
}
return 0;
}
总结
多个问题有相通的部分, 考虑全局的做一些操作
旧 \(\rm{trick}\) : 正难则反之后用容斥处理答案, 一般可以使用状态压缩的方法