查看原题请戳这里
看到这道题,最直观的就是对于每一种情况都跑一遍多重背包,然而这样做的话时间复杂度会变得非常奇妙……
首先,我们可以考虑每种钱的个数没有限制的方案数。很显然,我们可以直接跑一个完全背包。
那么,如果只有一种钱存在个数限制呢?
我们用$f[i]$表示支付金额$i$的方案数,那么$f[s]$就是硬币没有限制时支付的方案数。如果面值为$c[i]$的硬币的数量为$d[i]$,那么只用c[i]可以支付的最大金额就是$c[i]\times d[i]$。那么,如果我们先花掉$d[i]+1$张面额为$c[i]$的钱,然后再用这$4$种钱去支付剩余的金额,那么无论剩余金额的支付方案是什么,这个放案一定会使用至少$d[i]+1$张$c[i]$,即$c[i]$的使用数量一定会超出限制。
我们再来考虑这些钱全部有数量限制时的情况。
如果我们对于每一种钱都按上面的方案处理,那么对于某种即超出$c[i]$的限制,又超出$c[j]$限制的方案数,我们就会减两遍,所以这里我们需要容斥一下。
code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include<iostream> #include<cstring> #include<cstdio>
using namespace std;
long long c[5],d[5],ans,tot,s,f[100005],k;
int main() { for(int i = 1; i <= 4; i++) scanf("%d",&c[i]); scanf("%d",&tot); 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] + f[i - c[j]]; while(tot--) { for(int i = 1; i <= 4; i++) scanf("%d",&d[i]); scanf("%d",&s); ans = f[s]; for(int k = 15;k > 0; k--) { long long u = 0,o = k,now = 0,cnt = 0; while(o > 0) { cnt++; if(o & 1) { now = (d[cnt] + 1) * c[cnt] + now; u ^= 1; } o >>= 1; } if(now > s) continue; if(u) ans -= f[s - now]; else ans += f[s - now]; } printf("%lld\n",ans); } return 0; }
|