Solution [HAOI2008]硬币购物
题目大意:给定(4)种硬币的面值,多次询问,给定(4)种硬币的数目,求有多少种方法拼凑出面值(S)
背包、容斥
分析:
先给出暴力做法(我一开始就是这么做的)
类似生成函数,对于每种面值为(c),数量为(d)的硬币,我们将(x^c,x^{2c},x^{3c},cdots,x^{dc})的系数置为(1),暴力卷积四次即可
单次(O(S^2))直接上天,NTT和FFT应该也是过不了的
这个时候我们可以发挥一下人类智慧
假定只有两种硬币,面值分别为(c_1,c_2),分别有(d_1,d_2)种,那么它们拼凑(S)的方案数,实际上就是不定方程(c_1x+c_2y=S)的满足(xin[0,d_1],yin[0,d_2])的解的个数
这个是非常好算的,(ax+by=c),假如有特解(x_0,y_0),那么所有通解为(x=x_0+kfrac{b}{(a,b)},y=y_0-kfrac{a}{(a,b)}),列个不等式就可以算出(k)的个数即解的个数
人类智慧算出前两个和后两个硬币的方案数,我们只需要求卷积后的第(S)项,那么可以(O(S))求
本机开(O2)可以(900ms)出结果,洛谷可以获得(60pts)的好成绩,理论上循环展开之类的卡一下常数是可以跑过去的,代码附在最末
然后正解,假设我们不考虑任何数量限制,那么这就是一个完全背包,可以直接(O(s))预处理出(f[n]),表示不考虑任何数量限制下凑出(n)面值的方案数
然后我们减去非法方案,也就是第一个超限,第二个超限,一直到第四个超限,它们的方案并
这个可以容斥原理
如果钦定第一个超限,它的数量为(d),面值为(c),那么我们的方案数就是(f[S-c(d+1)]),其它同理
正解:
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 100;
int c[8],d[8],s,n;
ll f[maxn] = {1ll};
inline void solve(){
cin >> d[1] >> d[2] >> d[3] >> d[4] >> s;
ll ans = f[s];
for(int now = 1;now <= ((1 << 4) - 1);now++){
int flg = -1,tmp = s;
for(int i = 0;i < 4;i++)
if((now >> i) & 1){
flg *= -1;
tmp -= (d[i + 1] + 1) * c[i + 1];
}
if(tmp >= 0)ans += flg * f[tmp];
}
cout << ans << '
';
}
int main(){
cin >> c[1] >> c[2] >> c[3] >> c[4] >> n;
for(int i = 1;i <= 4;i++)
for(int j = c[i];j < maxn;j++)
f[j] += f[j - c[i]];
while(n--)solve();
return 0;
}
暴力:(貌似现在禁掉了指令集,不然AVX512是随便跑的/kk)
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e5 + 100;
typedef long long ll;
inline int exgcd(int a,int b,int &x,int &y){
if(!b){
x = 1,y = 0;
return a;
}
int res = exgcd(b,a % b,y,x);
y -= (a / b) * x;
return res;
}
inline int ceil1(int a,int b){return (a + b - 1) / b;}
inline int floor1(int a,int b){return (a / b);}
inline int fh(int x){return x >= 0 ? 1 : -1;}
inline int abs(int x){return x >= 0 ? x : -x;}
inline int ceil(int a,int b){
if(!a)return 0;
if(fh(a) * fh(b) == 1)return ceil1(a,b);
else return -floor1(abs(a),abs(b));
}
inline int floor(int a,int b){
if(!a)return 0;
if(fh(a) * fh(b) == 1)return floor1(a,b);
else return -ceil1(abs(a),abs(b));
}
ll ans;
int c[8],d[8],a,b,n,s,x,y,dd,aa,bb;
int tmp[4][maxn];
inline int solve(int c,int d1,int d2){
int x = ::x,y = ::y,L = -0x7fffffff,R = 0x7fffffff;
if(c % dd)return 0;
x *= c / dd;
y *= c / dd;
L = max(L,ceil(-x,aa));
R = min(R,floor(d1 - x,aa));
if(L > R)return 0;
L = max(L,ceil(y - d2,bb));
R = min(R,floor(y,bb));
if(L > R)return 0;
return R - L + 1;
}
inline void solve(){
scanf("%d %d %d %d %d",d + 1,d + 2,d + 3,d + 4,&s);
ans = 0;
a = c[1],b = c[2];
dd = exgcd(a,b,x,y),aa = b / dd,bb = (a / dd);
for(int i = 0;i <= s;i++)
if(d[1] * c[1] + d[2] * c[2] >= i)tmp[1][i] = solve(i,d[1],d[2]);
else tmp[1][i] = 0;
a = c[3],b = c[4];
dd = exgcd(a,b,x,y),aa = b / dd,bb = (a / dd);
for(int i = 0;i <= s;i++)
if(d[3] * c[3] + d[4] * c[4] >= i)tmp[2][i] = solve(i,d[3],d[4]);
else tmp[2][i] = 0;
for(int i = 0;i <= s;i++)
ans += tmp[1][i] * tmp[2][s - i];
printf("%lld
",ans);
}
int main(){
scanf("%d %d %d %d %d",c + 1,c + 2,c + 3,c + 4,&n);
while(n--)solve();
return 0;
}