bzoj1042【HAOI2008】硬币购物

1042: [HAOI2008]硬币购物

Time Limit: 10 Sec   Memory Limit: 162 MB
Submit: 1835   Solved: 1074
[ Submit][ Status][ Discuss]

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



容斥原理+背包,思路好题

用完全背包可以求出f[i],表示不限定硬币数量,组成i的方案数。

直接求不好求,可以运用补集思想,ans=不限定硬币的方案数-有硬币超出的方案数。

那有硬币超出的方案数sum怎么求呢?

根据容斥原理有:sum=至少一种超出的方案数-至少两种超出的方案数+至少三种超出的方案数-全部超出的方案数。

然后对于每一步,某几种硬币至少超出限定数量,也就是至少有d[i]+1个,剩余的价值rest=s-∑(c[i]*(d[i]+1)),方案数即为f[rest],f数组已经用完全背包预处理了。




#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#define F(i,j,n) for(int i=j;i<=n;i++)
#define D(i,j,n) for(int i=j;i>=n;i--)
#define ll long long
#define maxn 100005
using namespace std;
int tot,n,c[10],d[10];
ll ans,f[maxn];
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
ll get(int x1=0,int x2=0,int x3=0,int x4=0)
{
	int sum=(d[x1]+1)*c[x1]+(d[x2]+1)*c[x2]+(d[x3]+1)*c[x3]+(d[x4]+1)*c[x4];
	return sum<=n?f[n-sum]:0;
}
int main()
{
	F(i,1,4) c[i]=read();tot=read();
	f[0]=1;
	F(i,1,4) F(j,c[i],100000) f[j]+=f[j-c[i]];
	while (tot--)
	{
		F(i,1,4) d[i]=read();n=read();
		ans=get();
		ans-=get(1)+get(2)+get(3)+get(4);
		ans+=get(1,2)+get(1,3)+get(1,4)+get(2,3)+get(2,4)+get(3,4);
		ans-=get(2,3,4)+get(1,3,4)+get(1,2,4)+get(1,2,3);
		ans+=get(1,2,3,4);
		printf("%lld\n",ans);
	}
}


你可能感兴趣的:(容斥原理,bzoj)