给定 和一个长 的序列 ,求满足以下条件的长 的序列 的个数
- ;
- 两两不同;
- 可以被 整除;
对 取模。
,。
若 则连接无向边 ,则两两不同的限制相当于图中没有边。
考虑容斥,设 为 的 的方案数。钦定某些点在同一个连通块,不难发现相同大小的连通块的容斥系数是相同的。不妨设大小为 的连通块的容斥系数为 ,则有转移:
考虑 需要满足的条件,设 表示所有 个点的无向图的容斥系数的总和,那么枚举图中边的个数,有:
而枚举 所在的连通块大小,有:
而 。
所以有:
那么 。
那么直接 dp 即可,时间复杂度 ,代码如下:
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int S=20,BS=1<<16,p=998244353;
int n;
ll m,a[S];
ll lcm[BS];
int f[S],dp[BS];
#define popc __builtin_popcount
inline ll gcd(ll x,ll y)
{
if(x==0||y==0) return x+y;
ll t=x%y;
while(t!=0) x=y,y=t,t=x%y;
return y;
}
inline ll getlcm(int st)
{
ll res=1;
for(int i=1;i<=n;i++)
{
if(st>>i-1&1)
{
ll g=gcd(res,a[i]);
res/=g;
if(res>m/a[i]) res=m+1;
else res*=a[i];
}
}
return res;
}
int main()
{
scanf("%d%lld",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=0;i<(1<<n);i++) lcm[i]=getlcm(i);
f[1]=1;
for(int i=2;i<=n;i++) f[i]=p-1ll*(i-1)*f[i-1]%p;
dp[0]=1;
for(int i=1;i<(1<<n);i++)
{
for(int j=i;j>0;j=(j-1)&i)
{
if((i&-i)!=(j&-j)) continue;
dp[i]=(dp[i]+1ll*f[popc(j)]*((m/lcm[j])%p)%p*dp[i^j]%p)%p;
}
}
printf("%d\n",dp[(1<<n)-1]);
return 0;
}