題解 P1450 【[HAOI2008]硬幣購物】
阿新 • • 發佈:2020-10-09
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\)
這個是非常好算的,\(ax+by=c\),假如有特解\(x_0,y_0\),那麼所有通解為\(x=x_0+k\frac{b}{(a,b)},y=y_0-k\frac{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 << '\n'; } 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\n",ans);
}
int main(){
scanf("%d %d %d %d %d",c + 1,c + 2,c + 3,c + 4,&n);
while(n--)solve();
return 0;
}