转化一下,相当于从$0$跳到$M$,每一步跳跃距离为$v_i$中的某个,每次跳跃距离不大于上一次,统计方案数
用$f_{i,j,k}$表示跳到$i$,第一步跳$v_j$,最后一步跳$\geq v_k$的方案数,那么有转移$f_{a+b,i,j}=\sum\limits_{k=1}^nf_{a,i,k}f_{b,k,j}$,跟矩阵乘法一模一样,所以我们可以把每个$f_i$看成矩阵,用矩阵乘法转移
先预处理出$f_{v_{1\cdots n}}$,显然$f_{v_1,1,1}=1,f_{v_1,i,j}=0$,然后有$f_{v_i}=f_{v_{i-1}}^{\frac{v_i}{v_{i-1}}}+A_i$(其中$A_i$的第$i$行的前$i$位是$1$,其它位置是$0$)因为$v_i$是$v_{i-1}$的倍数,再加上从起点一步跳到$v_i$这种方法,所以有这样的转移
最后来统计答案,也就是计算$f_M$,直接从$v_n$到$v_1$贪心地取转移就好了
为什么?假如有这样一种贪心跳法:一直跳$v_n$直到不能跳,然后跳$v_{n-1}$,以此类推,假设它经过的点叫做“关键点”,那么关键点一定被所有不同的跳法所经过,因为这些跳法都是把贪心跳法中的大步换成小步得到的($v_i$中两两互为倍数或因数),这样就说明了以上计算$f_M$的方法是正确的
#include<stdio.h>
#include<string.h>
const int mod=998244353;
typedef long long ll;
int mul(int a,int b){return a*(ll)b%mod;}
int ad(int a,int b){return(a+b)%mod;}
int n;
struct matrix{
int a[51][51];
matrix(){memset(a,0,sizeof(a));}
}t[51],ans;
matrix operator*(matrix a,matrix b){
int i,j,k;
matrix c;
for(i=1;i<=n;i++){
for(j=1;j<=n;j++){
for(k=1;k<=n;k++)c.a[i][j]=ad(c.a[i][j],mul(a.a[i][k],b.a[k][j]));
}
}
return c;
}
matrix pow(matrix a,ll b){
matrix s;
for(int i=1;i<=n;i++)s.a[i][i]=1;
while(b){
if(b&1)s=s*a;
a=a*a;
b>>=1;
}
return s;
}
ll v[51];
int main(){
ll m,k;
int i,j;
scanf("%d%lld",&n,&m);
for(i=1;i<=n;i++)scanf("%lld",v+i);
t[1].a[1][1]=1;
for(i=2;i<=n;i++){
k=v[i]/v[i-1];
t[i]=pow(t[i-1],k);
for(j=1;j<=i;j++)t[i].a[i][j]++;
}
for(i=1;i<=n;i++)ans.a[i][i]=1;
for(i=n;i>0;i--){
k=m/v[i];
m%=v[i];
ans=ans*pow(t[i],k);
}
k=0;
for(i=1;i<=n;i++)k=ad(k,ans.a[i][1]);
printf("%lld",k);
}