洛谷1450:硬币购物
题目描述:
- 有四种面值的硬币,面值分别为(c1,c2,c3,c4)。
- 某人去商店买东西,一共去了(T)次,每次带(d_i)枚(c_i)硬币,买价值为(s_i)的东西,问每次有多少种付款方法。
数据范围:
- (d_i,s_i leq 1e5)
- (totleq 1000)
思路:
- 背包+容斥
- 对于每一次询问都做一次多重背包,肯定会超时。
- 暂且先考虑如果每种硬币可以无限次的使用会发生什么情况。
- 那就是完全背包,可以用(f(i))来表示每种硬币选择任意多次的方案数,有转移方程(f(j)=f(j-c(i)))。
- 先来考虑只有一种硬币的情况。
- 这里的方案数可能是超过了(d)个的,也就是说结果可能是存在有使用了(d+1,d+2,...)种方案的情况,所以我们需要把他减去。
- 这里要怎么做呢,先举个例子,比如说我现在有两个区间([2,+infin])和([3,+infin])。我想要得到([2,3])区间我只需要将这两个区间相减即可。
- 那么我们只需要考虑选则任意数量的方案,减去选则(d+1)个硬币的方案。
- 那么可以知道(ans = f(s)-(f(s)-c*(d+1)))。这样就是选择无限的减去选则(d+1)的。
- 对于多种硬币,就有(ans=f(s)-sum_{i=1}^{4}f(s-c_i*(d_i+1)))。
- 但是这个式子只是意会用的,他是不能直接累加的,因为可能种类(1)的硬币超出限制,种类(2)的硬币超出限制,而直接累加会导致冗余情况。
- 所以需要容斥定理,当然这里只有四种硬币,所以可以直接枚举,但是用位运算枚举会方便很多。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
int c[5], d[5], s, T;
ll f[maxn], ans;
int main()
{
for(int i = 1; i <= 4; i++) scanf("%d", &c[i]);
f[0] = 1;
for(int i = 1; i <= 4; i++)
for(int j = c[i]; j <= maxn-5; j++)
f[j] += f[j-c[i]];
scanf("%d", &T);
while(T--)
{
ll ans = 0;
for(int i = 1; i <= 4; i++)
scanf("%d", &d[i]);
scanf("%d", &s);
ans = f[s];
for(int i = 1; i < (1<<4); i++)
{
ll tmp = 0, num = 0;
for(int j = 1; j <= 4; j++)
{
if((1<<(j-1))&i)
{
num++;
tmp += (d[j]+1) * c[j];
}
}
if(tmp > s) continue;
if(num % 2 == 0) ans += f[s-tmp];
else ans -= f[s-tmp];
}
cout << ans << endl;
}
return 0;
}