思路
- 题目求大于 M 的个数,感觉乘积大于 M 的可以到很大,不好弄,可以考虑转化成求乘积小于等于 M 的子集个数。(用总子集个数 2^N 减去它就行了)
- 考虑背包,令
f[x]
代表乘积等于 x
的子集个数,可以一个个元素往里加,每次更新一遍dp数组。
最初始的暴力这个样子,无论空间、时间都是平方级别。
for (i=1; i<=N; i++)
for (j=1; j<=M; j++)
f[i][j]=f[i-1][j]+f[i-1][j/a[i]];
- 思考哪里可以优化,首先,每次需要修改的项其实挺少,可以考虑只算需要修改的 j ;其次,由于是多重集,可以考虑合并处理相同值的元素;再加上类似滚动数组的操作,就有了如下代码。
代码:
#include <cstdio>
#include <cstring>
#define Ha 998244353
#define Max 1000000
typedef long long LL;
using namespace std;
LL N,M,c[Max+5],f[Max+5],a[Max+5],b[Max+5],ans,ss;
LL jc[1000005];
LL ksm(LL x,LL n)
{
LL ret=1;
for (LL i=1,tmp=x; n; ) {
if (n&i) {
ret=ret*tmp%Ha;
n^=i;
}
tmp=tmp*tmp%Ha;
i<<=1;
}
return ret;
}
LL C(LL x,LL y)
{
LL ret=jc[x];
ret=ret*ksm(jc[y],Ha-2)%Ha;
ret=ret*ksm(jc[x-y],Ha-2)%Ha;
return ret;
}
int main()
{
jc[0]=jc[1]=1;
for (LL i=2; i<=1000000; i++) jc[i]=jc[i-1]*i%Ha;
scanf("%lld%lld",&N,&M);
for (LL i=1; i<=N; i++){
scanf("%lld",&a[i]);
c[a[i]]++;
}
f[1]=ksm(2,c[1]);
for (LL i=2,cc,nn=N; i<=Max && nn; i++){
for (LL k=i,tmp=1; k<=M && tmp<=c[i]; k=k*i,tmp++){
cc=C(c[i],tmp);
for (LL j=M/k; j; j--){
b[j*k]=(b[j*k]+f[j]*cc)%Ha;
}
}
for (LL j=M/i; j; j--){
f[j*i]=(f[j*i]+b[j*i])%Ha;
b[j*i]=0;
}
nn-=c[i];
}
for (LL i=1; i<=M; i++)
ss=(ss+f[i])%Ha;
ans=ksm(2,N);
ans-=ss;
ans=(ans%Ha+Ha)%Ha;
printf("%lld",ans);
return 0;
}