思路
题目求大于 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 ;
}