洛谷 P1450 HAOI 2008 硬币购物

题目描述

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

输入格式

第一行 c1,c2,c3,c4,tot 下面tot行 d1,d2,d3,d4,s

输出格式

每次的方法数

说明

di,s<=100000

tot<=1000

样例 

输入

1 2 5 10 2
3 2 3 1 10
1000 2 2 2 900

输出

4
27

这个题很容易可以想到用多重背包去解决,但是时间不允许呀!!!

考虑用完全背包代替多重背包,首先完全背包求出没有硬币限制的凑成 s 的方案数,然后陷入沉思,然后用无限制的方案数减去不合法的(限制条件)方案数就是结果。

考虑不合法的方案数,有四种硬币,考虑每一个硬币是否数量超了,有15种可能,1 2 3 4分别超过了,1 和 2, 2 和 3 ......很明显可以用容斥原理解决此题,不合法的方案数就是:

超出一个硬币的方案数 - 超出两个的方案数 + 超出三个的方案数 - 超出四个的方案数

 再用总方案数dp[S]减去上述值即可。

再考虑不合法的方案数怎么求???

不合法有四种可能,上面说了,分别是超出一个,两个......对于每一种,假设1不合法,那么在完全背包中,1至少被放入了d[1] + 1次,我们多选一个硬币1,那么现在硬币1有d[1] + 1个了,一定超额了,那么剩余的背包空间无论怎么填,硬币1都会超额,所以硬币1超额的方案数就是 dp[S - c[i] * (d[i]) + 1],然后就是容斥原理大法了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int c[6], d[6], tot, s;
ll res;
const int N = 1e5 + 10;
ll dp[N];
ll cal(ll n){
	return c[n] * d[n] + c[n];
}
int main()
{
	scanf("%d %d %d %d %d", &c[1], &c[2], &c[3], &c[4], &tot);
	while (tot--){
		scanf("%d %d %d %d %d", &d[1], &d[2], &d[3], &d[4], &s);
		memset(dp, 0, sizeof (dp));
		dp[0] = 1;
		for (int i = 1; i <= 4; i++){
			for (int j = c[i]; j <= s; j++){
				dp[j] += dp[j - c[i]];
			}
		}
		ll res = dp[s];
		if(cal(1) <= s)res -= dp[s - cal(1)];
		if(cal(2) <= s)res -= dp[s - cal(2)];
		if(cal(3) <= s)res -= dp[s - cal(3)];
		if(cal(4) <= s)res -= dp[s - cal(4)];
		if(cal(1) + cal(2) <= s)res += dp[s - cal(1) - cal(2)];
		if(cal(1) + cal(3) <= s)res += dp[s - cal(1) - cal(3)];
		if(cal(1) + cal(4) <= s)res += dp[s - cal(1) - cal(4)];
		if(cal(3) + cal(2) <= s)res += dp[s - cal(3) - cal(2)];
		if(cal(4) + cal(2) <= s)res += dp[s - cal(4) - cal(2)];
		if(cal(3) + cal(4) <= s)res += dp[s - cal(3) - cal(4)];
		if(cal(1) + cal(2) + cal(3) <= s)res -= dp[s - cal(1) - cal(2) - cal(3)];
		if(cal(1) + cal(2) + cal(4) <= s)res -= dp[s - cal(1) - cal(2) - cal(4)];
		if(cal(1) + cal(4) + cal(3) <= s)res -= dp[s - cal(1) - cal(4) - cal(3)];
		if(cal(4) + cal(2) + cal(3) <= s)res -= dp[s - cal(4) - cal(2) - cal(3)];
		if(cal(1) + cal(2) + cal(3) + cal(4) <= s)res += dp[s - cal(1) - cal(2) - cal(3) - cal(4)];
		printf("%lld\n", res);
	}
	return 0;
}

打表是一种浪漫

posted @ 2019-09-18 22:33  correct  阅读(166)  评论(0)    收藏  举报