$[HAOI2008]$硬币购物

\(\mathcal{\color{red}{Description}}\)

硬币购物一共有\(4\)种硬币。面值分别为\(c_1,c_2,c_3,c_4\)。某人去商店买东西,去了\(tot\)次。每次带\(d_i\)\(c_i\)硬币,买\(s_i\)的价值的东西。请问每次有多少种付款方法。

\(\mathcal{\color{red}{Solution}}\)

好的,比较欣喜的一点是我们如果不考虑什么带了\(k_i\) 个,那么其实就是一个完全背包是不是……但是他有一个特别\(zz\)的限制条件。那我们不妨考虑从总方案数里面减去不合法的方案。但是吧,会有重复减这种情况,所以我们还要容斥。

那么套用容斥原理的基本公式,就可以得到\(Ans\)

还有一点,就是如何计算不合法的方案呢?

我们思考对于完全背包的递推式大概长这样:\[f_i = \sum\limits_{j=1}^{4}f_{i-c_j}\]

那么也就是对于所有不合法的状态比如\(f_{i - (d_j + t \times c_j)},t \geq 1\)都会有着这样的递推式\[f_{i-(d_j+T) \times c_j} = \sum \limits_{t \geq T}f_{i - (d_j + t \times c_j)}\]

那么也就是说对于最终结果,就存在\(f_{i - ((d_j + 1) \times c_j)}\) 里面

// luogu-judger-enable-o2
#include <cstdio>
#include <iostream>
#define MAXN 5
#define MAX 100010
#define ll long long

using namespace std ; ll T, Ans ;
ll N, C[5], F[MAX], D[5], S, i, j, k ;

inline ll qr(){
    ll k = 0 ; char c = getchar() ;
    while(!isdigit(c)) c = getchar() ;
    while(isdigit(c)) k = (k << 1) + (k << 3) + c - 48, c = getchar() ;
    return k ;
}
inline ll WORK(ll X){
    ll Y = S, Mark = 1 ;
    for (k = 0 ;k < 4 ;++ k)
        if ((X >> k) & 1)
            Y -= (D[k + 1] + 1) * C[k + 1], Mark = -Mark ; 
    return Y < 0 ? 0 : F[Y] * Mark ;
} 
int main(){
    F[0] = 1 ;
    for (i = 1 ; i <= 4 ; ++ i) cin >> C[i] ;
    for (i = 1 ; i <= 4 ; ++ i)
        for (j = C[i] ; j < MAX ; ++ j)
            F[j] += F[j - C[i]] ; cin >> T ;
    for (i = 1 ; i <= T ; ++ i){
        Ans = 0, D[1] = qr(), D[2] = qr(), D[3] = qr(), D[4] = qr(), S = qr() ;
        for (j = 0 ; j < 16 ; ++ j) Ans += WORK(j) ;
        printf("%lld\n", Ans) ;
    }
    return 0 ;
}

转载于:https://www.cnblogs.com/pks-t/p/9503867.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值