题目描述
有(n)种面值不同的硬币,每种有无限个,且任意两个((x,y))要么(x)是(y)的倍数,要么(y)是(x)的倍数。
你要取(m)元钱,问你有多少种不同的取法。
(nleq 50,mleq {10}^{18})
题解
假设面值为(a_1,a_2,ldots,a_n)
先把所有硬币按面值从小到大排序。
那么考虑从小到大取钱。
如果前面(i)种面值已经取完了,那么取的钱数(mod)(a_{i+1})已经确定了。
有这么一个DP:设(f_i(x))为取完了前面(i)种面值的硬币,取的钱数为(xa_i+mmod a_i)的方案数。
转移:枚举(i)这种硬币用了多少个(或者说剩下了多少个):
[egin{align}
f_i(x)&=sum_{j=0}^x f_{i-1}(frac{ja_i+mmod a_i}{a_{i-1}})\
&=sum_{j=0}^x f_{i-1}(bj+c)\
end{align}
]
我们很容易发现(f_i(x))是一个(i)次函数。
那么只需要求(f_i(0)ldots f_i(i))就可以了。
每次可以通过线性插值求出。
时间复杂度:(O(n^3))
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int p=998244353;
ll fp(ll a,ll b){ll s=1;for(;b;b>>=1,a=a*a%p)if(b&1)s=s*a%p;return s;}
int f[60][60];
int ifac[60];
int inv[60];
int pp[60];
int *pre=pp+1;
int suf[60];
ll a[60];
int n;
ll m;
int c[60];
int gao(int id,ll x)
{
if(x<=id)
return f[id][x];
x%=p;
pre[-1]=1;
for(int i=0;i<=id;i++)
pre[i]=ll(x-i)*pre[i-1]%p;
suf[id+1]=1;
for(int i=id;i>=0;i--)
suf[i]=ll(x-i)*suf[i+1]%p;
ll s=0;
for(int i=0;i<=id;i++)
s+=(ll)f[id][i]*pre[i-1]%p*suf[i+1]%p*ifac[i]%p*ifac[id-i]%p*((id-i)&1?-1:1);
return s%p;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
#endif
scanf("%d%lld",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
ifac[0]=ifac[1]=inv[1]=1;
for(int i=2;i<=n;i++)
{
inv[i]=(ll)-p/i*inv[p%i]%p;
ifac[i]=(ll)ifac[i-1]*inv[i]%p;
}
f[1][0]=1;
f[1][1]=1;
for(int i=2;i<=n;i++)
for(int j=0;j<=i;j++)
f[i][j]=(f[i][j-1]+gao(i-1,(j*a[i]+m%a[i])/a[i-1]))%p;
int ans=gao(n,m/a[n]);
ans=(ans+p)%p;
printf("%lld
",ans);
return 0;
}