[HAOI2008]硬币购物
做题时间:2022.8.4
\(【题目描述】\)
有 \(4\) 中硬币,每一种面值为 \(c_i(c_i\leq 10^5)\) ,某人去了 \(n(n\leq 10^5)\) 次商店买东西,每次购买他带了 \(d_i(d_i\leq 10^5)\) 枚面值为 \(c_i\) 的硬币,想购买价值为 \(S(S\leq 10^5)\) 的物品,问有多少种付款方式。
\(【输入格式】\)
第一行五个整数, 表示 \(4\) 种硬币面值和 \(n\)
接下来 \(n\) 行,每行五个整数表示 \(4\) 种硬币的数量和要买的东西的面值
\(【输出格式】\)
每行一个整数表示答案。
\(【考点】\)
容斥、背包
【做法】
直接DP肯定不太行,翻译一下题目条件,相当于求解不定方程:
\[\sum\limits_{i=1}^4 c_i\cdot x_i=S,c_i\leq d_i
\]
若没有限制,那就是一个典型的完全背包求解。带上限制后,相当于要求同时满足所有限制,直接求解不好做,使用容斥(具体见:容斥原理之带限制的不定方程求解 ),最后去除限制得到:
\[\sum\limits_{i=1}^4 c_i\cdot x_i=S-\sum\limits_{i=1}^k c_i\cdot (b_{a_i}+1)
\]
其中 \(A_i\) 是一个 \(k\) 元集合,且 \(A_i=\{x|x\in \N_+,1\leq x\leq n\}\)
然后用完全背包求解即可。
具体实现的时候,预处理出全集 \(|U|\) ,即不带任何限制的方案。对于每一次购买,用二进制的方法枚举 \(a_i\) ,同时根据其 \(1\) 的个数判断系数是正是负。
\(【代码】\)
#include<cstdio>
#include<iomanip>
using namespace std;
typedef long long ll;//容斥原理的题一般都要long long
const int N=1e5+50;
int n,c[5],d[5],S;
ll f[N];
int main()
{
for(int i=1;i<=4;i++) scanf("%d",&c[i]);
scanf("%d",&n);
f[0]=1;
for(int i=1;i<=4;i++){
for(int j=1;j<N;j++) if(j>=c[i]) f[j]+=f[j-c[i]];
}//完全背包预处理
while(n--){
for(int i=1;i<=4;i++) scanf("%d",&d[i]);
scanf("%d",&S);
ll ans=0;
for(int k=1;k<(1<<4);k++){//用二进制下k的第i位表示A中是否包含i
int bit=0;
ll cnt=0;
for(int i=1;i<=4;i++){
if((1<<i-1)&k){//若k的第i位为1,表示当前这一项包含i
cnt+=(d[i]+1)*c[i];
bit++;
}
}
if(S-cnt>=0) ans+=(bit%2==0 ? (-1) : 1) * f[S-cnt];//容斥公式的系数
}
printf("%lld\n",f[S]-ans);//全集-补集的并
}
return 0;
}

浙公网安备 33010602011771号