Catalog
题解 P1450 【[HAOI2008]硬币购物】

查看原题请戳这里

看到这道题,最直观的就是对于每一种情况都跑一遍多重背包,然而这样做的话时间复杂度会变得非常奇妙……

首先,我们可以考虑每种钱的个数没有限制的方案数。很显然,我们可以直接跑一个完全背包。

那么,如果只有一种钱存在个数限制呢?

我们用$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;
}
Author: wflight
Link: http://yoursite.com/2019/10/24/题解-P1450-【-HAOI2008-硬币购物】/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
  • 支付寶

Comment