P1450 [HAOI2008]硬币购物

题目描述
硬币购物一共有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;
    }
posted @ 2018-11-03 08:40  Tony_Double_Sky  阅读(173)  评论(0编辑  收藏  举报