P1450 (容斥 + DP)
题意:
共有 4 种硬币。面值分别为 c1,c2,c3,c4。
某人去商店买东西,去了n 次,对于每次购买,他带了 di 枚种硬币,想购买 s 的价值的东西。请问每次有多少种付款方法。
思路:
看起来像是一个多重背包求方案数的模板题,但是由于有 n 组,所以每次都求一次多重背包绝对会超时。
显然多重背包即有限制的方案数,直接计算有限制的方案数比较难,考虑正难则反:
有限制的方案数 = 无限的方案数 - 超过限制方案数。
首先无限制的方案数显然就是完全背包,我们可以先用完全背包预处理出所有的方案数,即 f [ i ] 表示购买价值为 i 的方案数。
考虑减去超过显示的即不合法的部分,考虑使用容斥原理。
首先考虑一种硬币超额使用的方案数怎么计算。若第 i 种硬币超额使用,即超过了原定的 \(d_j\) 个硬币的限制,我们可以先强制选了\(d_j + 1\)个第 j 种硬币,这样剩下的价值里,4 种硬币随便选,这样就能保证第 j 种硬币一定超额使用,第 j 种硬币超额的不合法的方案数就等于\(f[s - (d_j + 1) * c_j ]\)。
然后我们只需要使用容斥原理奇加偶减即可。加上一种硬币不合法的方案数,减去两种硬币不合法的方案数,加上三种硬币不合法的方案数。二进制枚举子集即可。
AC代码:
// -----------------
//#pragma GCC optimize(2)
#include <iostream>
#include <cstring>
#include <algorithm>
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(0);
#define endl '\n'
#define rep(i,x,y) for(int i = (x); i <= (y); i ++)
#define dec(i,y,x) for(int i = (y); i >= (x); i --)
#define int long long
using namespace std;
const int N = 1e5 + 7;
int f[N],c[100],d[100];
int s;
void solve()
{
rep(i,1,4) cin >> d[i];
cin >> s;
int ans = f[s], now;
for(int i = 1; i < (1 << 4); i ++)
{
now = s;
int ff = 0;
for(int tmp = i,j = 1; tmp; tmp >>= 1,j ++)
{
if(tmp & 1)
{
ff ^= 1;
now -= (d[j] + 1) * c[j];
}
}
if(now >= 0)
{
if(ff) ans -= f[now];
else ans += f[now];
}
}
cout << ans << endl;
}
signed main()
{
IOS
int T = 1;
rep(i,1,4) cin >> c[i];
cin >> T;
f[0] = 1;
rep(i,1,4)
rep(j,c[i],N-8)
f[j] += f[j - c[i]];
while(T --) { solve(); }
return 0;
}

浙公网安备 33010602011771号