硬币购物一共有4种硬币。面值分别为c1,c2,c3,c4。某人去商店买东西,去了tot次。每次带di枚ci硬币,买si的价值的东西。请问每次有多少种付款方法。
硬币购物一共有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
dp+容斥原理。
首先可以预处理f[i]表示在四种硬币没有限制的情况下买i的东西有几种付款方式。f[i]=sigma(f[i-c[k]]) (1<=k<=4)
一定要注意:一种硬币算完再算第二种!!!
即for (int i=1;i<=4;i++) for (int j=1;j<=maxcost;j++)!!
因为反过来就会有重复!!!
接下来就可以容斥了:
得到面值S的超过限制的方案数 - 第1种硬币超过限制的方案数 - 第2种硬币超过限制的方案数 - 第3种硬币超过限制的方案数 - 第4种硬币超过限制的方案数 + 第1,2种硬币同时超过限制的方案数 + 第1,3种硬币同时超过限制的方案数 + ...... + 第1,2,3,4种硬币全部同时超过限制的方案数。
(from byvoid)
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <cstdlib> #define M 100005 #define LL long long using namespace std; LL f[M],ans; int d[5],c[5],t,s; LL F(int x) { return x>=0?f[x]:0LL; } void RC() { for (int i=1;i<=4;i++) ans-=F(s-c[i]*(d[i]+1)); for (int i=1;i<=4;i++) for (int j=i+1;j<=4;j++) ans+=F(s-c[i]*(d[i]+1)-c[j]*(d[j]+1)); int now; for (int i=1;i<=4;i++) { now=0; for (int j=1;j<=4;j++) if (j!=i) now+=c[j]*(d[j]+1); ans-=F(s-now); } ans+=F(s-now-c[4]*(d[4]+1)); } int main() { scanf("%d%d%d%d%d",&c[1],&c[2],&c[3],&c[4],&t); f[0]=1; for (int j=1;j<=4;j++) for (int i=1;i<=100000;i++) if (i>=c[j]) f[i]+=f[i-c[j]]; while (t--) { for (int i=1;i<=4;i++) scanf("%d",&d[i]); scanf("%d",&s); ans=f[s]; RC(); printf("%lld\n",ans); } return 0; }
感悟:
1.因为只有四种面值的硬币,所以容斥非常好算
2.一定要注意dp的循环嵌套,防止重复!!
3.byvoid题解