P1450 [HAOI2008] 硬币购物 题解
P1450 [HAOI2008] 硬币购物 题解
首先考虑只有一种硬币的情况。
如果取的数量没有限制,就是一个完全背包,\(f_i\) 表示背包体积为 \(i\) 的选择方案数,显然 \(f_j = f_{j - v}\)。
如果取的数量有限制,用多重背包做一遍会超时,考虑以下思路:所有方案数 - 不合法方案数 = 合法方案数。
所有的方案数显然就是 \(f_s\),而不合法的方案数就是 \(f_{s- c\times(d+1)}\),原因如下:
最多选择 \(d\) 次硬币,如果选择了 \(d + 1\) 个硬币就是不合法的情况,所以我们先选 \(d + 1\) 个硬币出来,剩下的体积随便放,都是不合法的情况 ,而这 \(d + 1\) 个硬币的体积占用是 \(c\times (d + 1)\)。剩下的体积就是 \(f_{s - c\times(d + 1)}\)。
所以合法的方案数就是 \(f_s - f_{s-c\times(d + 1)}\)。
考虑有多种硬币的情况,如果有很多种硬币,答案不能简单的相加,因为这样会算重复,比如算硬币 \(1\) 不合法的情况中已经包含了硬币 \(1\) 和硬币 \(2\) 都不合法的情况,如果再直接减去硬币 \(2\) 不合法的情况,硬币 \(1\) 和硬币 \(2\) 都不合法的情况相当于被减去了两次,所以需要再加上硬币 \(1\) 和硬币 \(2\) 都不合法的情况 \(f_{s - c_1\times(d_1+1)-c_2\times(d_2+1)}\)。
所以考虑容斥原理做一下就完美去重了。
时间复杂度:\(O(s+n2^V)\),\(V\) 是硬币种数,此题为 \(4\)。
// Problem: P1450 [HAOI2008] 硬币购物
// Contest: Luogu
// Author: Moyou
// Copyright (c) 2023 Moyou All rights reserved.
// Date: 2023-09-01 19:32:55
#include <algorithm>
#include <iostream>
#define int long long
using namespace std;
const int N = 1e5 + 10;
int n, v[5], w[5], dp[N];
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> v[1] >> v[2] >> v[3] >> v[4] >> n;
dp[0] = 1;
for(int i = 1; i <= 4; i ++)
for(int j = v[i]; j <= N - 10; j ++) dp[j] += dp[j - v[i]];
for(int i = 1, s, ans, sum; i <= n; i ++) {
cin >> w[1] >> w[2] >> w[3] >> w[4] >> s;
ans = 0;
for(int i = 0; i < (1 << 4); i ++) {
sum = s;
for(int j = 0; j < 4; j ++)
if((i & (1 << j))) sum -= (w[j + 1] + 1) * v[j + 1];
if(sum >= 0) ans += ((__builtin_popcount(i) & 1) ? -1 : 1) * dp[sum];
}
cout << ans << '\n';
}
return 0;
}