BZOJ 1042: [HAOI2008]硬币购物 DP,与处理,容斥

Description

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

  第一行 c1,c2,c3,c4,tot 下面tot行 d1,d2,d3,d4,s,其中di,s<=100000,tot<=1000
Output

  每次的方法数
Sample Input
1 2 5 10 2

3 2 3 1 10

1000 2 2 2 900
Sample Output
4

27
解题方法: 容斥好题,首先不考虑数量限制,dp[i][j]表示点 i 种硬币组成价值为 j 的物品的方案数,则有 dp[0][0]=0,dp[i][j]=dp[i
-1][j]+dp[i][j-c[i],最终答案就是 dp[4][n]。我们考虑个数限制,则答案应为任何一种都无限制的方案,减去
第一种硬币个数超了的方案,减去第二种硬币个数超了的方案,减去第三种硬币个数超了的方案,减去第四种硬
币个数超了的方案,加上第一种,第二种硬币个数超了的方案,……,加上第一种,第二种,第三种,第四种硬
币个数超了的方案,总之就是个容斥原理。我们考虑如何计算第一种硬币个数超了的方案,设面值为 c,数量为
d,则我们必须要用第一种硬币选出(d+1)×c 的价值,然后剩下的 n-(d+1)×c 随便选,因此第一种硬币个数超
了的方案就是 dp[4][n-(d+1)×c],剩下情况类似,这样每次询问的复杂度就做到了 O(1)。

代码如下:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int S = 100010;
#define rep(i, a, b) for(int i = a; i <= b; i++)
int c[5], d[5];
int n;
LL f[S], ans;
void dfs(int x, int k, int sum) //
{
    if(sum < 0) return ;
    if(x == 5){
        if(k & 1) ans -= f[sum];
        else ans += f[sum];
        return ;
    }
    dfs(x + 1, k + 1, sum - (d[x] + 1) * c[x]);
    dfs(x + 1, k, sum);
}
int main(){
    scanf("%d%d%d%d%d", &c[1], &c[2], &c[3], &c[4], &n);
    f[0] = 1;
    rep(i, 1, 4){
        rep(j, c[i], S - 1){
            f[j] += f[j - c[i]];
        }
    }
    rep(i, 1, n){
        int x;
        scanf("%d%d%d%d", &d[1], &d[2], &d[3], &d[4]);
        scanf("%d", &x);
        ans = 0;
        dfs(1, 0, x);
        printf("%lld\n", ans);
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值