ABC236Ex - Distinct Multiples
ABC236Ex - Distinct Multiples
好题!点集容斥转边集容斥,加深了对容斥的理解。与 GDKOI 2023 D1T3 做法类似。
首先发现 \(A\) 互不相同的限制不好处理,考虑使用容斥适当放宽限制。如果我们将这 \(n\) 个元素看作 \(n\) 个点的图,一条连边 \((u,v)\) 代表 \(A_u,A_v\) 相同,那么实际上最后要求图中没有任意一条边。
考虑设 \(f_e\) 表示钦定图中已经存在集合 \(e\) 的边,不管其他边是否存在的满足条件 \(1,3\) 合法方案数。方案数是好算的,只需要将每个连通块中的 \(D_i\) 的 \(\text{lcm}\) 算出来,然后计算有多少个倍数 \(\le M\) 即可。接下来考虑计算容斥系数,设 \(\mu(e)\) 表示钦定集合 \(e\) 的边的容斥系数,由子集反演容易得到 \(\mu(e)=(-1)^{|e|}\)。
但是这样容斥子集的个数是 \(2^{n^2}\) 级别的,显然无法接受。
接下来考虑优化,注意到我们的方案数实际上只与连通块有关,所以我们尝试直接对连通块进行容斥。
那么对于一种将 \(n\) 个点划分为若干个连通块 \(s_1,s_2,\dots,s_k\) 的方案,我们计算其容斥系数。由于这 \(k\) 个连通块互不相关,所以这个划分的容斥系数等价于这 \(k\) 个连通块容斥系数贡献的乘积。那么接下来我们只需要算出一个连通块 \(s\) 的容斥系数即可。由于图是完全图,所以任意一个点数相同的连通块容斥系数均相同,那么我们只需要算出点数为 \(i(i\in [1,n])\) 的容斥系数即可。
设 \(g_i\) 表示大小为 \(i\) 的集合 \(s\) 的容斥系数。我们套用上面的边集容斥,那么对于集合 \(s\),其贡献实际上是满足所有边的两端点在 \(s\) 的点集中且使得 \(s\) 中的点连通的边集的容斥系数之和,其中我们知道一个合法的边集的系数为 \((-1)^{|e|}\)。
由于直接计算使得 \(s\) 连通的边集的系数比较困难,考虑用所有方案的系数减去 \(s\) 中点不连通的方案的系数。总系数为 \(\sum_{j=0}^{|e|} \binom{|e|}{j}(-1)^j=[|e|=0]=[i=1]\),不连通的系数即考虑与 \(1\) 相连的连通块大小,可以得到
其中 \(g_j\) 为与 \(1\) 相连的连通块的系数,\([i-j=1]\) 为剩下的 \(i-j\) 个点随便连的总系数。
那么可以得到
简单化简一下可以得到 \(g_s=(-1)^{i-1}(i-1)!\)。
设 \(f_s\) 表示点集 \(s\) 满足条件 \(1,3\) 的方案数,那么我们需要对于每种划分的方案中每个点集的容斥系数乘上方案数的乘积。那么只需要处理出来每个点集的 \(f_s\times g_S\),直接暴力枚举子集 \(\text{dp}\) 即可。(注意:转移时需要钦定 \(s\) 中最小的元素必选)
时间复杂度 \(\mathcal{O}(3^n)\)。 使用集合幂级数 \(\text{exp}\) 可以做到更优。
code
#include<bits/stdc++.h>
using namespace std;
namespace IO{
template<typename T>inline bool read(T &x){
x=0;
char ch=getchar();
bool flag=0,ret=0;
while(ch<'0'||ch>'9') flag=flag||(ch=='-'),ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(),ret=1;
x=flag?-x:x;
return ret;
}
template<typename T,typename ...Args>inline bool read(T& a,Args& ...args){
return read(a)&&read(args...);
}
template<typename T>void prt(T x){
if(x>9) prt(x/10);
putchar(x%10+'0');
}
template<typename T>inline void put(T x){
if(x<0) putchar('-'),x=-x;
prt(x);
}
template<typename T>inline void put(char ch,T x){
if(x<0) putchar('-'),x=-x;
prt(x);
putchar(ch);
}
template<typename T,typename ...Args>inline void put(T a,Args ...args){
put(a);
put(args...);
}
template<typename T,typename ...Args>inline void put(const char ch,T a,Args ...args){
put(ch,a);
put(ch,args...);
}
inline void put(string s){
for(int i=0,sz=s.length();i<sz;i++) putchar(s[i]);
}
inline void put(const char* s){
for(int i=0,sz=strlen(s);i<sz;i++) putchar(s[i]);
}
}
using namespace IO;
#define N (1<<16)
#define M 17
#define p 998244353
#define ll long long
inline int add(int x,int y){
return x+y>=p?x+y-p:x+y;
}
inline int mul(int x,int y){
return (long long)x*y%p;
}
inline int power(int x,int y){
int res=1;
while(y){
if(y&1) res=mul(res,x);
x=mul(x,x),y>>=1;
}
return res;
}
int n,val[N],f[N],low[N],fac[M];
ll d[N];
ll m,w[M];
inline ll gcd(ll x,ll y){
return !y?x:gcd(y,x%y);
}
inline ll lcm(ll x,ll y){
__int128 res=(__int128)x/gcd(x,y)*y;
return res>m?m+1:res;
}
int main(){
read(n,m),fac[0]=1;
for(int i=1;i<=n;i++) read(w[i]);
for(int i=1;i<=n;i++) fac[i]=mul(fac[i-1],i);
for(int s=1;s<(1<<n);s++){
int k=__builtin_popcount(s);
low[s]=(s&1)?1:low[s>>1]+1;
if(k==1) d[s]=w[low[s]];
else d[s]=lcm(w[low[s]],d[s^(s&-s)]);
val[s]=mul(m/d[s]%p,fac[k-1]);
if(!(k&1)) val[s]=add(0,p-val[s]);
}
f[0]=1;
for(int s=1;s<(1<<n);s++)
for(int t=s;t;t=(t-1)&s)
if(low[s]==low[t]) f[s]=add(f[s],mul(val[t],f[s^t]));
put('\n',f[(1<<n)-1]);
return 0;
}