[HAOI2008] 硬币购物
[HAOI2008] 硬币购物
题目描述
共有 \(4\) 种硬币。面值分别为 \(c_1,c_2,c_3,c_4\)。
某人去商店买东西,去了 \(n\) 次,对于每次购买,他带了 \(d_i\) 枚 \(i\) 种硬币,想购买 \(s\) 的价值的东西。请问每次有多少种付款方法。
输入格式
输入的第一行是五个整数,分别代表 \(c_1,c_2,c_3,c_4, n\)。
接下来 \(n\) 行,每行有五个整数,描述一次购买,分别代表 \(d_1, d_2, d_3, d_4,s\)。
输出格式
对于每次购买,输出一行一个整数代表答案。
样例 #1
样例输入 #1
1 2 5 10 2
3 2 3 1 10
1000 2 2 2 900
样例输出 #1
4
27
提示
数据规模与约定
- 对于 \(100\%\) 的数据,保证 \(1 \leq c_i, d_i, s \leq 10^5\),\(1 \leq n \leq 1000\)。
[HAOI2008] 硬币购物
分析
第一眼看到题目,许多硬币组成一个价值的方案,限制个数,指定花费,这不就是多重背包求方案数吗?
然后注意到多次询问,每次都要做多重背包,就算是单调队列优化,复杂度也是 \(O(4ns)\)。显然出题人是专门卡多重背包的,多重背包的思路一定是没前途的。这样就要换一种思路。
先分别考虑每种硬币,有限制不好搞,可以考虑用全部状态减去非法状态。
设 \(f_j\) 表示每个硬币可用无数次,得到价值为 \(j\) 的方案数。显然状态转移就是 \(f_j=f_j+f_{j-c}\)
非法状态就是硬币至少用了 \(d+1\) 个,将这 \(d+1\) 个硬币视为最后加上的,显然 \(f_j\) 就是由 \(f_{j-(d+1) \times c}\) 转移来的。
也就是说合法方案 \(ans=f_j-f_{j-(d+1) \times c }\)
同理,四种硬币也是总状态减去非法状态,不过四种硬币的非法情况的方案数会有重叠,这就要用容斥原理了。
设第 \(i\) 种硬币的非法情况的方案数为 \(A_i\)。
则
最后用 \(f_j\) 减去即可。
总之一句话 \(无限制-一个硬币的限制+两个硬币的限制-三个硬币的限制+四个硬币的限制\)
可以用壮压的方法枚举子集来进行容斥计算 \(state\) 的第 \(i\) 位为 \(1\),则该状态有第 \(i+1\) 种硬币的限制。
code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5,M=10;
int n,s;
int c[M],d[M];
ll f[N],ans;
int main ()
{
for(int i=1;i<=4;i++) cin>>c[i];
f[0]=1;
for(int i=1;i<=4;i++)
for(int j=c[i];j<N;j++)
f[j]=f[j]+f[j-c[i]];
cin>>n;
for(int i=1;i<=n;i++)
{
for(int i=1;i<=4;i++) cin>>d[i];
cin>>s;
ans=0;
for(int i=0;i<(1<<4);i++)
{
int cnt=0;
ll t=0;
for(int j=1;j<=4;j++) if(i&(1<<(j-1))) cnt++,t+=c[j]*(d[j]+1);
if(s>=t)
{
if(cnt&1) ans-=f[s-t];
else ans+=f[s-t];
}
}
cout<<ans<<"\n";
}
return 0;
}
部分语言参考:https://www.cnblogs.com/LLTYYC/p/10773808.html
https://www.luogu.com.cn/article/ywr15b0z