洛谷P1450 [HAOI2008]硬币购物【背包+容斥】

本文探讨了一种特殊的背包问题——硬币购物问题,通过使用完全背包预处理并结合容斥原理来解决限制条件下的支付方案数量计算。文章详细解释了如何在给定硬币面额、携带数量和购买价值的情况下,利用容斥原理消除非法支付方案,最终得出有效的支付方式数目。
摘要由CSDN通过智能技术生成

Time Limit: 10 Sec Memory Limit: 162 MB

Description

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

Input

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

Output

每次的方法数


题目分析

第一眼单调队列优化多重背包,但再一看数据范围emmmm

首先假如没有物品个数的限制,那么这就是一个完全背包,这样的情况可以直接预处理
现在考虑加入限制,当前 d p [ S i ] dp[S_i] dp[Si]中包含的超过 d i d_i di限制的方案数为 d p [ S i − c i ∗ ( d i + 1 ) ] dp[S_i-c_i*(d_i+1)] dp[Sici(di+1)]
相当于先强制往包中放入 d i + 1 d_i+1 di+1 c i c_i ci剩余 S i − c i ∗ ( d i + 1 ) S_i-c_i*(d_i+1) Sici(di+1)的容积不管怎么放都已经是不合法的了

先令 a n s = d p [ S i ] ans=dp[S_i] ans=dp[Si],然后 a n s − = ∑ i = 1 4 d p [ S i − c i ( d i + 1 ) ] ans-=\sum_{i=1}^4dp[S_i-c_i(d_i+1)] ans=i=14dp[Sici(di+1)]
但是这时候我们又发现 c i , c j ( i ! = j ) c_i,c_j(i!=j) ci,cj(i!=j)同时不合法的方案数多减了,我们再加回来
这时叒发现 c i , c j , c k ( i ! = j ! = k ) c_i,c_j,c_k(i!=j!=k) ci,cj,ck(i!=j!=k)同时不合法的方案数多加了,我们再减掉
以及叕加回四个同时不合法的方案数

所以就是个容斥问题
一般容斥都可以转化成二进制简化运算
复杂度约为 O ( 4 ∗ m a x n + T ∗ 2 4 ) O(4*maxn+T*2^4) O(4maxn+T24)


#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
typedef long long lt;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=100010;
int T,n;
int ci[5],di[5];
lt dp[maxn],S;

int main()
{
    for(int i=1;i<=4;++i) ci[i]=read();
    
    dp[0]=1;
    for(int i=1;i<=4;++i)
    for(int j=ci[i];j<maxn;++j)
    dp[j]+=dp[j-ci[i]];
    
    T=read();
    while(T--)
    {
        for(int i=1;i<=4;++i) di[i]=read();
        S=read(); lt res=dp[S];
        for(int i=1;i<=(1<<4)-1;++i)
        {
            int tt=S,cnt=0;
            for(int j=1;j<=4;++j)
            if(i&(1<<j-1)) cnt++,tt-=ci[j]*(di[j]+1); 
            if(tt<0) continue;
            if(cnt&1) res-=dp[tt];
            else res+=dp[tt];
        }
        printf("%lld\n",res);
    }
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值