视频加载失败

安吉D4-A

808 字
4 分钟
安吉D4-A
原题呈现

P1450 硬币购物#

题目描述#

共有 44 种硬币.面值分别为 c1,c2,c3,c4c_1,c_2,c_3,c_4

某人去商店买东西,去了 nn 次,对于每次购买,他带了 did_iii 种硬币,想购买 ss 的价值的东西.请问每次有多少种付款方法.

输入格式#

输入的第一行是五个整数,分别代表 c1,c2,c3,c4,nc_1,c_2,c_3,c_4, n

接下来 nn 行,每行有五个整数,描述一次购买,分别代表 d1,d2,d3,d4,sd_1, d_2, d_3, d_4,s

输出格式#

对于每次购买,输出一行一个整数代表答案.

输入输出样例 #1#

输入 #1#

1 2 5 10 2
3 2 3 1 10
1000 2 2 2 900

输出 #1#

4
27

说明/提示#

数据规模与约定#

  • 对于 100%100\% 的数据,保证 1ci,di,s1051 \leq c_i, d_i, s \leq 10^51n10001 \leq n \leq 1000

我们发现这道题是一个物品数很少的多重背包问题,但显然,使用多重背包会超时.

因此,我们尝试将其转化为完全背包.

观察到完全背包和多重背包的区别只在于限制,我们先无视限制,跑一次完全背包.

dp[0] = 1;
for (int i = 1; i <= 4; i++) {
for (int j = c[i]; j <= N; j++) {
dp[j] += dp[j - c[i]];
}
}

显然,这样的方案数比目标方案数大,多出了:

至少有 1 种数值的硬币超出限制

这样的方案.

为了计算出多出的方案数,我们可以使用容斥.定义 f(s)f(s) 表示第 ii 种硬币(ss 是一个集合且满足 isi \in s)必须超出限制,其余不管.

则至少有 1 种数值的硬币超出限制的答案 ansans

ans=f({1})+f({2})+f({3})+f({4})(f({1,2})+f({1,3})+f({1,4})+f({2,3})+f({2,4})+f({3,4}))+f({1,2,3})+f({1,2,4})+f({1,3,4})+f({2,3,4})f({1,2,3,4})\begin{align*} ans &= f(\{1\}) + f(\{2\}) + f(\{3\}) + f(\{4\})\\ &-(f(\{1,2\}) + f(\{1,3\}) + f(\{1,4\}) + f(\{2,3\}) + f(\{2,4\}) + f(\{3,4\}))\\ &+f(\{1,2,3\})+f(\{1,2,4\})+f(\{1,3,4\})+f(\{2,3,4\})\\ &-f(\{1,2,3,4\}) \end{align*}

最终答案为:dp[s]ansdp[s] - ans

问题是,如何求 f(s)f(s)

f({2,4})f(\{2,4\}) 为例,需要统计第 2 种和第 4 种硬币超出限制的方案数,那么,只需要固定第 2 种有 d[2]+1d[2]+1 个,用掉 (d[2]+1)×c[2](d[2] + 1) \times c[2] 元,第 4 种有 c[4]c[4] 个,用掉 (d[4]+1)×c[4](d[4] + 1) \times c[4] 元,将这些当成已知,剩余的需要分配的元数为 s=s(d[2]+1)×c[2](d[4]+1)×c[4]s' = s - (d[2] + 1) \times c[2] - (d[4] + 1) \times c[4]f({2,4})f(\{2,4\}) 就等于 ss'

标程

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 100;
int c1, c2, c3, c4, d1, d2, d3, d4, d5, s;
int dp[N], c[5], d[5];
// dp[i][j] 考虑到前i种硬币,总价值为j的方案数
// 不存在硬币超出 = 无限制 - (必须1种超出) + (必须2种超出) - (必须3种超出) +
// (必须4种超出)
signed main() {
cin >> c[1] >> c[2] >> c[3] >> c[4];
int T;
cin >> T;
dp[0] = 1;
for (int i = 1; i <= 4; i++) {
for (int j = c[i]; j <= N; j++) {
dp[j] += dp[j - c[i]];
}
}
while (T--) {
cin >> d[1] >> d[2] >> d[3] >> d[4] >> s;
int ans = 0;
// 子集枚举
for (int st = 0; st < (1 << 4); st++) {
int cur = s, vaild = 0;
for (int i = 0; i < 4; i++) {
if (st & (1 << i)) {
vaild++;
cur -= (d[i + 1] + 1) * c[i + 1];
}
}
if (cur >= 0)
ans += (vaild % 2 ? -1 : 1) * dp[cur];
}
cout << ans << endl;
}
}

正难则反,正着思考问题没有结果不妨反过来思考.

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

安吉D4-A
https://blog.jerrylab.top/posts/problem/anji2026/D4/A/
作者
Jerry
发布于
2026-08-04
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Jerry
Hello, I'm Jerry.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签
最新动态
站点统计
文章
80
分类
3
标签
35
总字数
89,198
运行时长
0
最后活动
0 天前
站点信息
构建平台
Cloudflare Pages
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0