首页 > 代码库 > [BZOJ 1042][HAOI 2008]硬币购物(背包+容斥原理)

[BZOJ 1042][HAOI 2008]硬币购物(背包+容斥原理)

题目链接:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1042

刚开始搞容斥原理,还很有点吃力,我太弱了。。。

首先用被类似于背包的DP进行预处理,假设每种硬币个数无限制,求出f[i]=凑出面值i的方案总数。

但是实际上题目中每种硬币个数是有限制的,设四种硬币分别是a、b、c、d,则凑出面值S的方案中超出限制的方案数=a超出限制的方案数+b超出限制的方案数+c超出限制的方案数+d超出限制的方案数-a和b都超出限制的方案数-a和c都超出限制的方案数......-c和d都超出限制的方案数+a、b、c都超出限制的方案数+a、c、d都超出限制的方案数+a、b、d都超出限制的方案数+b、c、d都超出限制的方案数-abcd都超出限制的方案数

然后对于每一次询问DFS,用容斥原理解决即可,dfs时用k做标记,标志当前情况是加还是减。

注:当dfs第i个硬币时,第i个硬币用了d[i]+1个,就意味着第i个硬币超出了限制,而剩余的硬币就可以随意分配,不受第i个硬币的个数的限制。

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>

#define MAXN 100050

using namespace std;

typedef long long int LL;

LL ans=0;
LL f[MAXN];
int c[5],d[5];

void dfs(int x,int k,int sum) //在用第x种硬币,还需要支付sum元的钱,k是容斥原理加还是减的标志
{
    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()
{
    for(int i=1;i<=4;i++) scanf("%d",&c[i]);
    int T;
    scanf("%d",&T);
    f[0]=1;
    for(int k=1;k<=4;k++) //先预处理出f[i]=所有硬币数量无限的情况下,获得面值i的方案数,为了避免重复计算,以k为阶段递推
        for(int i=c[k];i<MAXN;i++)
            f[i]+=f[i-c[k]];
    for(int i=1;i<=T;i++)
    {
        for(int i=1;i<=4;i++) scanf("%d",&d[i]);
        int x; //要买x元的商品
        ans=0;
        scanf("%d",&x);
        dfs(1,0,x);
        printf("%lld\n",ans);
    }
    return 0;
}

[BZOJ 1042][HAOI 2008]硬币购物(背包+容斥原理)