P1450 [HAOI2008]硬币购物

时间:2022-12-06 22:16:01

题目描述

硬币购物一共有4种硬币。面值分别为c1,c2,c3,c4。某人去商店买东西,去了tot次。每次带di枚ci硬币,买si的价值的东西。请问每次有多少种付款方法。

di,s<=100000

tot<=1000

Solution

完全背包数组开不下, 大概要运算一天这样能出答案

假设没有带硬币的限制, 我们可以搞个完全背包算出 \(maxn\) 内每个的方案数, 就可以 \(O(1)\) 回答询问了

问题是如何解决这个限制问题

对于第 \(i\) 个硬币, 我们只能拿 \(d_{i} * c_{i}\) 这么多钱

那就是说如果我拿了 \((d_{i} + 1) * c[i]\) 这么多钱则剩下的不合法

那么 \(dp[S - (d_{i} + 1) * c[i]]\) 便不合法

然后发现这样可能会减掉重复的

容斥一下, 减去单数个的加上偶数个的

只有 4 枚硬币, 可以状压枚举状态(0 - 15), 模拟做容斥即可

说不明白的可以看代码

Code

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#include<climits>
#define LL long long
#define REP(i, x, y) for(LL i = (x);i <= (y);i++)
using namespace std;
LL RD(){
LL out = 0,flag = 1;char c = getchar();
while(c < '0' || c >'9'){if(c == '-')flag = -1;c = getchar();}
while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
return flag * out;
}
const LL maxn = 200019;
LL c[7], T;//7777777
LL d[7], one[7], S;
LL dp[maxn];
void init(){
REP(i, 1, 4)c[i] = RD(), one[i] = 1 << (i - 1);
dp[0] = 1;
REP(i, 1, 4){
REP(j, c[i], maxn - 7){
dp[j] += dp[j - c[i]];
}
}
T = RD();
}
void solve(){
while(T--){
REP(i, 1, 4)d[i] = RD();
S = RD();
LL ans = 0;
REP(i, 0, 15){//每个状态
LL temp = S;
LL cnt = 0;
REP(j, 1, 4){
if(i & one[j])
temp -= (d[j] + 1) * c[j], cnt ^= 1;
}
if(temp < 0)continue;
if(!cnt)ans += dp[temp];
else ans -= dp[temp];
}
printf("%lld\n", ans);
}
}
int main(){
init();
solve();
return 0;
}