好像容斥原理在OI的应用挺广泛的啊……先奶一口再说
这题的容斥方法用的非常巧妙,定义
f[i]
表示价值为
i
的付款方案数,其中所有硬币的数量无限。那么我们直接做一遍完全背包即可,时间复杂度
然后就是重点:考虑容斥,每一次询问的答案
ans=S−S1−S2−S3−S4+S1,2+S1,3+S1,4+S2,3+S2,4+S3,4−S1,2,3−S1,2,4−S1,3,4−S2,3,4+S1,2,3,4
,其中
Sp
表示用集合
p
中的硬币组成{1.超过每种硬币数量限制;2.价值和为给定的
假设第一种硬币超过数量限制,也就是用到
d1+1
枚第一种硬币,剩余的硬币可以随意分配。如果
sum−(d1+1)×c1≥0
,方案数就是
f[sum−(d1+1)×c1]
,否则方案数为
0
。其余情况类似,只要对于每次询问用
附上AC代码:
#include <cstdio>
using namespace std;
typedef long long ll;
const int n=4;
int c[5],d[5],m,x;
ll f[100010],ans;
inline void so(int g,int k,int sum){
if (sum<0) return;
if (g>n) return (void)(ans-=(k?f[sum]:-f[sum]));
return so(g+1,k^1,sum-(d[g]+1)*c[g]),so(g+1,k,sum);
}
int main(void){
for (int i=1; i<=n; ++i) scanf("%d",&c[i]);
f[0]=1;
for (int i=1; i<=n; ++i)
for (int j=c[i]; j<=100000; ++j)
f[j]+=f[j-c[i]];
scanf("%d",&m);
while (m--){
for (int i=1; i<=n; ++i) scanf("%d",&d[i]);
scanf("%d",&x),ans=0,so(1,0,x),printf("%lld\n",ans);
}
return 0;
}