题意:有4种硬币,面值分别为c1,c2,c3,c4,然后给出Q组查询。每组查询给出5个数d1,d2,d3,d4,v,分别表示面值为ci的硬币共有di个,然后要求将其凑成总值为v的方案数。
数据范围大致是c<=1000,Q<=100,d,v<=100000。
如果按照普通分组背包的做法,对每组Q都做一遍DP,那么这个复杂度就会达到10^9的级别,肯定会TLE。我也拿不出太好的办法,觉得这题DP不出来。
其实,这题不完全是DP,我觉得这个题目的核心是容斥原理。对于每组查询,算法的复杂度基本上是O(1)这样的常数级的。
在UVA的论坛里看到了大牛的解释:
假设现在只有两种硬币,我们令N=所有凑出v总值的方案数,N1=第一种面值为c1的硬币取了超过d1次凑出总值v的方案数,N2=第二种面值为c2的硬币取了超过d2次凑出总值v的方案数,N12=第一种面值为c1的硬币取了超过d1次且N第二种面值为c2的硬币取了超过d2次凑出总值v的方案数。那么对于一组查询d1,d2,v,合法的方案数就是N-N1-N2+N12.
为了计算这些N,我们利用c1,c2,c3,c4先做一遍背包容量为100000的无限背包,用dp数组记录方案数。
那么计算N的话,直接算用dp[v]就可以了。
怎么计算N1呢?因为c1硬币取了超过了d1次,那么我们就假设它取了d1+1次,共取走了(d1+1)*c1价值的硬币,那么方案数就是dp[v-(d1+1)*c1]。
同理,N2的方案数就是dp[v-(d2+1)*c2],N12的方案数就是dp[v-(d1+1)*c1-(d2+1)*c2]。然后统计N-N1-N2+N12,就可以得到答案了。
现在问题中的硬币种类边成了4个,还是用一样的原理,这时候答案就是N-N1-N2-N3-N4+N12+N13+N14+N23+N24+N34-N123-N124-N134-N234+N1234,一共16项。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cstdio> 2 typedef long long LL; 3 LL dp[100010]; 4 5 int main(){ 6 int c[4],d[4],v,Q,kase; 7 scanf("%d",&kase); 8 while(kase--){ 9 dp[0] = 1; 10 for(int i = 1;i <= 100000;i++) dp[i] = 0; 11 12 for(int i = 0;i < 4;i++) 13 scanf("%d",&c[i]); 14 15 for(int i = 0;i < 4;i++) 16 for(int j = c[i];j <= 100000;j++) 17 dp[j] += dp[j-c[i]]; 18 19 scanf("%d",&Q); 20 21 while(Q--){ 22 LL ans = 0; 23 24 for(int i = 0;i < 4;i++) 25 scanf("%d",&d[i]); 26 27 scanf("%d",&v); 28 29 for(int i = 0;i < 16;i++){ 30 int tmp = v; 31 int flag = 1; 32 for(int j = 0;j < 4;j++){ 33 if(i&(1<<j)){ 34 flag = -flag; 35 tmp -= (d[j]+1) * c[j]; 36 } 37 } 38 if(tmp >= 0) ans += flag * dp[tmp]; 39 } 40 41 printf("%lld ",ans); 42 } 43 } 44 return 0; 45 }