题解:AT_abc349_f [ABC349F] Subsequence LCM
Part 1 题意
给定一个长度为
n 的序列,求出\operatorname{lcm} 为m 的子序列个数,数据范围n\le 2\times10^5 ,m\le 10^{16} ,a_i\le10^{16} 。Part 2 主要思路
做法:一点点数论内容 + 状压 dp。
首先,我们可以从另一个角度来考虑
对于两个正整数数
a,b ,设a=\sum_{i=1}^{k} p_{i}^{c_{p_i}} ,b=\sum_{i=1}^{l} q_{i}^{d_{q_i}} ,设集合S\in p \cup q ,那么\operatorname{lcm}(a,b)=\sum_{i\in S}S_i^{\max(c_{S_i},d_{S_i})} ,如此做我们可以通过维护S,c,d 三个集合来处理这个问题。
我们不难发现,
第一步:
排除一定不可能产生任何贡献的数,也就是排除所有无法整除
第二步:
将
第三步:
求出数组
我们回到原来那个式子。如果两个数的
由于我们使用了状压 dp,所以对于第
第四步:
进行状压 dp。
定义
我们令
因此当加入一个数
我们可以发现,这种转移形式可以转化为按位或运算。
那么有如下转移方案:
特别注意:
- 两个数的按位或会大于等于这两个数中的任意一个,所以我们为防止自我更新,应从大到小枚举状态。
- 如果
m 为1 ,记得减掉bxf_0 额外加的1 。
假设
Part 3 代码
#include<bits/stdc++.h>
#define mod 998244353
#define int long long
using namespace std;
int b[200005],f[200005],bxf[1<<15],xf,n,m;
struct zys{
int num;
int cnt;
};
vector<zys> x;
signed main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++){
int x;cin>>x;
if(m%x==0)b[++xf]=x;
}
int m2=m;
for(int i=2;i*i<=m2;i++){
if(m2%i==0)x.push_back({i,0});
while(m2%i==0){
x[x.size()-1].cnt++;
m2/=i;
}
}
if(m2>1){
x.push_back({m2,1});
}
//cout<<1;
//for(auto i:x)cout<<i.num<<" "<<i.cnt<<endl;
int cnt1,j2,cnt2;
for(int i=1;i<=xf;i++){
cnt2=0;
for(auto j:x){
cnt2++;
cnt1=0,j2=b[i],m2=m;
while(j2%j.num==0){
j2/=j.num;cnt1++;
}
if(cnt1==j.cnt)f[i]+=(1<<(cnt2-1));
}
}
bxf[0]=1;
for(int i=1;i<=xf;i++){
for(int j=(1<<x.size())-1;j>=0;j--){
bxf[f[i]|j]+=bxf[j];
if(bxf[f[i]|j]>=mod)bxf[f[i]|j]-=mod;
}
}
if(m==1)cout<<bxf[(1<<x.size())-1]-1;
else cout<<bxf[(1<<x.size())-1];
}