给定 n,m 和一个长 n 的序列 ai,求满足以下条件的长 n 的序列 b 的个数
- 1≤bi≤m;
- bi 两两不同;
- bi 可以被 ai 整除;
对 998244353 取模。
1≤n≤16,1≤ai≤m≤1018。
若 bi=bj 则连接无向边 (i,j),则两两不同的限制相当于图中没有边。
考虑容斥,设 dpS 为 i∈S 的 bi 的方案数。钦定某些点在同一个连通块,不难发现相同大小的连通块的容斥系数是相同的。不妨设大小为 n 的连通块的容斥系数为 fn,则有转移:
dpS=T⊆S,min{T}=min{S}∑f∣T∣⌊lcm{ai∣i∈T}m⌋dpS−T
考虑 fn 需要满足的条件,设 gn 表示所有 n 个点的无向图的容斥系数的总和,那么枚举图中边的个数,有:
gn=i=0∑2n(n−1)(i2n(n−1))(−1)i=i=0∑2n(n−1)(i2n(n−1))(−1)i1n−i=[n=0]+[n=1]
而枚举 n 所在的连通块大小,有:
gn=i=1∑n(i−1n−1)fign−i=fn+(n−1)fn−1
而 g1=f1=1。
所以有:
fn={1−(n−1)fn−1n=1n>1
那么 fn=(−1)n−1(n−1)!。
那么直接 dp 即可,时间复杂度 O(3n),代码如下:
#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;
}