传送门
子任务1:m=1
直接算两遍选或不选就可以了
期望得分:10
子任务2:n<=1e4 m<=5
两种方法
一。枚举每一个数,看它被几个数整除,它对答案有贡献当且仅当被选4k次(k∈N)所以一个数的贡献就是
时间复杂度:O(nm)
二。枚举m的子集,因为m<=5可以进行类似求n个数里不被某几个数整除的数的个数一样的容斥,只需要把4的容斥系数改成2就可以了。时间复杂度:O(3^m*m)
期望得分:30
子任务3:n<=1e9 m<=20
首先我们根据子任务二中枚举m的子集的方法拓展一下。
令f(s)表示[1,n]中多少数是s集合中的数的公倍数
即
g(s)表示[1,n]中恰好被s整除但不会被除s的子集外整除的数的个数
可以通过枚举子集来进行转移 时间复杂度O(3^n)
我们接下来开始优化
令F(i)表示 G(i)同理
这个可以理解成因为对于j>i所以j一定包含了C(j,i)个大小为i的子集所以我们把它减掉就可以了
这个其实就是把上面的G(j)改成以F的容斥所以长的差不多qwq
很明显F可以在O(2^m*m)的时间复杂度内计算完
G可以在O(m^2)的时间复杂度容斥
所以总复杂度为O(2^m*m)
期望得分100
附代码。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define inf 20021225
#define ll long long
#define mdn 998244353
#define mod(x) (x>=mdn?x-mdn:x)
using namespace std;
int m,a[23],n;
int G[23],f[23][23];
int gcd(int x,int y)
{
return y==0?x:gcd(y,x%y);
}
int count(int x)
{
int cnt=0;
while(x)
{
if(x&1) cnt++;
x>>=1;
}
return cnt;
}
void pre()
{
f[0][0]=f[1][0]=f[1][1]=1;
for(int i=2;i<=m;i++)
{
f[i][i]=f[i][0]=1;
for(int j=1;j<i;j++)
f[i][j]=mod(f[i-1][j]+f[i-1][j-1]);
}
}
int C(int x,int y)
{
return f[x][y];
}
int ksm(int bs,int mi)
{
int ans=1;
while(mi)
{
if(mi&1) ans=(ll)ans*bs%mdn;
bs=(ll)bs*bs%mdn;mi>>=1;
}
return ans;
}
int main()
{
int ans=0;
scanf("%d%d",&n,&m);pre();
for(int i=1;i<=m;i++) scanf("%d",&a[i]);
for(int i=1;i<(1<<m);i++)
{
int g=1,f,j,cnt=count(i);
for(j=0;j<m;j++)
{
if(i&(1<<j))
{
f=gcd(a[j+1],g);
if((ll)g/f*a[j+1]>n) break;
g=g/f*a[j+1];
}
}
if(j<m) continue;
else G[cnt]=mod(G[cnt]+n/g);
}
G[0]=n;
for(int i=m;~i;i--)
for(int j=i+1;j<=m;j++)
G[i]=mod(G[i]-(ll)G[j]*C(j,i)%mdn+mdn);
//for(int i=0;i<=m;i++) printf("%d
",G[i]);
for(int i=0;i<=m;i++)
for(int j=0;j<=i;j+=4)
ans=mod(ans+(ll)G[i]*C(i,j)%mdn*(1<<m-i)%mdn);
printf("%d
",(ll)ans*ksm((1<<m),mdn-2)%mdn);
return 0;
}