标签:return code use space amp std 状压 math haoi2008
共有四种硬币,其面值分别为\(c_1,c_2,c_3,c_4\)
\(n\)次询问,每次给定每种硬币的个数\(D_i\)和付款金额\(S\),问共有多少种付款方式
\(n≤10^3,S≤10^5\)
我们可以把问题看作做\(n\)次多重背包,用单调队列优化,最优的复杂度为\(O(n4s)\)在这里不详细讨论
对于每次询问,相当于给定四个限制,第\(i\)个限制即只能至多使用\(i\)个硬币支付。类似于错排,满足限制的方案数=全部方案数-不满足的方案数
(错排的容斥推导可见https://www.luogu.com.cn/blog/maxsama/zuhemath)
而对于全部的方案数,相当于做体积为\(S\)的完全背包,而\(S\)至多为\(10^5\),因此我们可以\(O(4S)\)预处理所有\(S\)。
而对于不满足条件的方案数
类似于错排,若不满足其中一个限制条件,一定是非法的,我们可以设\(A_i\)为第\(i\)种硬币不满足限制的方案集合。
那么不满足条件的方案数即
而对于\(|A_i|\),我们当前一共要支付\(S\)个硬币,而对于第\(i\)种只能支付\(D_i\)个,那么我们如果先把\(D_i+1\)个硬币付完剩下的支付方案(即从四种硬币中支付\(S-C_i(D_i+1)\)的金额)是一定不合法的,因为第\(i\)个硬币至少需要选零个,而如果选\(0\)个,最终还是多付了一个。
因此,第\(|A_i|=F[S-C_i(D_i+1)]\)
因为我们要求集合的并,因此,我们需要考虑容斥,而考虑容斥我们就需要求出集合的交
我们考虑任意最简单的情况:两个集合的交
我们从定义出发,\(A_i\)为第\(i\)种硬币不满足限制的方案集合,而\(A_j\)为第\(j\)种硬币不满足限制的方案集合。
而显然,同时满足\(i\)种硬币不满足限制与\(j\)种硬币不满足限制这样的性质的集合,为它们的交。
同样地,我们可以把他们两个的\(D_i+1\)都先支付出去,那么则有
那么
那么我们可以状压枚举\(T\)算出答案。
总的复杂度为\(O(4S+16n)\)
代码:
#include <iostream>
using namespace std;
const int maxn = 10;
int c[maxn],d[maxn];
long long f[100010];
int main(){
int n;
cin >> c[1] >> c[2] >> c[3] >> c[4] >> n;
f[0] = 1;
for (int i = 1; i <= 4; i++){
for (int j = c[i]; j <= 100010; j++){
f[j] += f[j - c[i]];
}
}
while(n--){
int s;
cin >> d[1] >> d[2] >> d[3] >> d[4] >> s;
long long ans = f[s];
for (int i = 0; i < 1 << 4; i++){
long long sum = 0,cnt = 0;
for (int j = 0; j < 4; j++){
if (i >> j & 1){
sum += c[j + 1] * (d[j + 1] + 1);
cnt++;
}
}
//cout << sum << endl;
long long t = s - sum;
if (cnt % 2 == 0 && t >= 0 && sum != 0) ans += f[s - sum];
else if (t >= 0 && sum != 0) ans -= f[s - sum];
}
cout << ans << endl;
}
//system("PAUSE");
return 0;
}
标签:return code use space amp std 状压 math haoi2008
原文地址:https://www.cnblogs.com/wxy-max/p/13237728.html