Description
共有 4 4 4 种硬币。面值分别为 c 1 , c 2 , c 3 , c 4 c_1,c_2,c_3,c_4 c1,c2,c3,c4。
某人去商店买东西,去了 n n n 次,对于每次购买,他带了 d i d_i di 枚 i i i 种硬币,想购买 s s s 的价值的东西。请问每次有多少种付款方法。
Input
输入的第一行是五个整数,分别代表 c 1 , c 2 , c 3 , c 4 , n c_1,c_2,c_3,c_4, n c1,c2,c3,c4,n。
接下来 n n n 行,每行有五个整数,描述一次购买,分别代表 d 1 , d 2 , d 3 , d 4 , s d_1, d_2, d_3, d_4,s d1,d2,d3,d4,s。
Output
对于每次购买,输出一行一个整数代表答案。
Solution
1.利用容斥定理,先用完全背包计算无限枚硬币的方案数。
实现如下:
// 完全背包模板
dp[0] = 1;
for(int i=1; i<=4; i++)
for(int j=c[i]; j<=100000; j++)
dp[j] += dp[j-c[i]];
该方案得到的是,肯定包含有四种硬币都不合法的方案数。
2.再由容斥定理,处理不合法的方案。
令四种硬币不合法的方案分别为A、B、C、D,由容斥定理不难得出公式:
P ( A ⋃ B ⋃ C ⋃ D ) = P ( A ) + P ( B ) + P ( C ) + P ( D ) − P ( A ⋃ B ) − P ( A ⋃ C ) − P ( A ⋃ D ) − P ( B ⋃ C ) − P ( B ⋃ D ) − P ( C ⋃ D ) + P ( A ⋃ B ⋃ C ) + P ( A ⋃ B ⋃ D ) + P ( A ⋃ C ⋃ D ) + P ( B ⋃ C ⋃ D ) − P ( A ⋃ B ⋃ C ⋃ D ) P(A\bigcup B\bigcup C\bigcup D)\\=P(A)+P(B)+P(C)+P(D)\\-P(A\bigcup B)-P(A\bigcup C)-P(A\bigcup D)\\-P(B\bigcup C)-P(B\bigcup D)-P(C\bigcup D)\\+P(A\bigcup B\bigcup C)+P(A\bigcup B\bigcup D)\\+P(A\bigcup C\bigcup D)+P(B\bigcup C\bigcup D)\\-P(A\bigcup B\bigcup C\bigcup D) P(A⋃B⋃C⋃D)=P(A)+P(B)+P(C)+P(D)−P(A⋃B)−P(A⋃C)−P(A⋃D)−P(B⋃C)−P(B⋃D)−P(C⋃D)+P(A⋃B⋃C)+P(A⋃B⋃D)+P(A⋃C⋃D)+P(B⋃C⋃D)−P(A⋃B⋃C⋃D)
现在的任务就是要在答案中减去不合法的方案,
即对于上述式子中的各个值,对答案贡献的为对应值的相反数。
3.模拟
可以通过二进制的模拟方式,1表示该币种不合法,0表示合法。
显然,需要让该币种不合法,就至少需要使用 d j + 1 d_j+1 dj+1枚,贡献价值 ( d j + 1 ) ∗ c [ j ] (d_j+1)*c[j] (dj+1)∗c[j] ,多种币种不合法就将该值相加即可。
币种多少对答案的贡献:由2得,奇数为负,偶数为正。
初始答案为完全背包后的 d p [ s ] dp[s] dp[s] 的值。
Code
#include <bits/stdc++.h>
typedef long long ll;
#define MOD 1000000007
#define intmax 2147483647
#define memmax 0x7fffffff
using namespace std;
inline ll read()
{
ll x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){
if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){
x=x*10+ch-48;ch=getchar();}
return x*f;
}
ll n, s;
ll c[5], d[5];
ll dp[100005];
void solve()
{
cin >> d[1] >> d[2] >> d[3] >> d[4] >> s;
ll ans = dp[s];// 无限硬币的答案
// 完全背包的答案相当于把四个硬币的不合法方案全部计算进去了
// 模拟四种硬币不合法方案(二进制形式)
// 保证总有一项为1
for(int i=1; i<(1<<4); i++)
{
// 判正负
ll cnt = 1, sum = 0;
for(int j=1; j<=4; j++)
{
// 检测不合法位
if(i>>(j-1) & 1)
{
cnt = -cnt;//+-取反
// 满足不合法至少需要的硬币枚数以及总价值
sum += (d[j]+1)*c[j];
}
}
// 大于要求的答案,对答案没影响
if(sum > s)
continue;
// 更新答案
ans += cnt*dp[s-sum];
}
cout << ans << endl;
}
int main(void)
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> c[1] >> c[2] >> c[3] >> c[4] >> n;
// 完全背包模板
dp[0] = 1;
for(int i=1; i<=4; i++)
for(int j=c[i]; j<=100000; j++)
dp[j] += dp[j-c[i]];
for(int i=1; i<=n; i++)
solve();
return 0;
}