bzoj1402:[HAOI2008]硬币购物

思路:完全背包加容斥原理

首先不考虑限制,那么很容易可以预处理出f[i](f[i]+=f[i-c[i]],1<=i<=4,i-c[i]>=0)。

然后考虑如何求出限制后的答案。

首先考虑这样的一个问题:x1+x2+x3+x4+x5+...+xn=m有多少组整数解。显然插板法可以解决这个问题,但如果引入对于xi的限制,令xi不能超过ri,那么这个问题就应该要用到容斥原理了。

令Si为所有满足条件的xi的集合,那么这个问题就转化为了求所有Si的交集后再用插板法的一个问题了,瓶颈就在于如何求出Si的交集,于是可以考虑容斥原理,Si的交集即全集U-所有Si补集的并集,而Si的补集也就是满足xi>ri即xi>=ri+1的xi的集合,这样令所有的xi-=(ri+1),也就是令m+=(ri+1),然后即可用容斥原理加插板法求出所有Si补集的并集,全集U即原始问题的答案,那么这样运用容斥就完美地解决了这样一个问题。

再回到我们的问题,可以发现这就是刚刚提到的问题的每一个xi乘上一个权值,那么就令m+=(ri+1)*ci即可,于是对于所有询问均可做到O(1)回答。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 using namespace std;
 7 #define maxn 101000
 8  
 9 int c[5],d[5],num[100],cases;
10 long long f[maxn];
11  
12 int main(){
13     for (int i=1;i<=4;i++) scanf("%d",&c[i]);scanf("%d",&cases);
14     f[0]=1;
15     for (int i=1;i<=4;i++)
16         for (int j=c[i];j<=100000;j++)
17             f[j]+=f[j-c[i]];
18     num[0]=1;
19     for (int i=1;i<(1<<4);i++) num[i]=num[i>>1]*((i&1)?-1:1);
20     while (cases--){
21         int sum;for (int i=1;i<=4;i++) scanf("%d",&d[i]);scanf("%d",&sum);long long ans=f[sum];
22         for (int i=1;i<(1<<4);i++){
23             int tmp=0;
24             for (int j=0;j<5;j++)
25                 if ((1<<j)&i) tmp+=(d[j+1]+1)*c[j+1];
26             if (sum>=tmp) ans+=f[sum-tmp]*num[i];
27         }
28         printf("%lld
",ans);
29     }
30     return 0;
31 }
View Code
原文地址:https://www.cnblogs.com/DUXT/p/5944604.html