$[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 ;
}
posted @ 2018-08-20 09:46  皎月半洒花  阅读(161)  评论(0编辑  收藏  举报