[51nod1375]再选数

本文探讨了一种求解在给定整数集合中选取特定数量元素,使其最大公约数为1的方案总数的方法。利用反演技巧进行高效计算,并通过实例展示了具体的实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

给出n个数,{a},求在其中选k个数使其gcd为1的方案数。
如果k=-1表示任意取,不过至少得取一个。
答案对998244353取模。
n<=10^5,ai<=10^6

Solution

记得某位dalao说过,看到gcd想到反演=w=
但这道题都能用反演?!涨姿势了~
开始一直在想容斥,想不出来,然后去Orz了栋爷的题解(数论蒟蒻不要鄙视我QwQ
然后看到第一句话就震惊了233
话说反演真的很好推呀!
设Fd表示gcd为d的方案数。
Gd表示gcd为d的倍数的方案数。
然后对每个数分解因数,求出有多少个数是d的倍数,那么用组合数就可以算出C了。
对于k=-1的情况预处理2的幂就好了,记得要-1,因为必须至少选一个。
然后就没有了,劲啊

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int N=1e6+5,mo=998244353;
int fact[N],inv[N],mi[N],cnt[N],g[N],mx,n,k,x,ans;
int p[N],mu[N];
bool bz[N];
int c(int m,int n) {
    if (n>m) return 0;
    return (ll)fact[m]*inv[n]%mo*inv[m-n]%mo;
}
int main() {
    scanf("%d%d",&n,&k);
    fo(i,1,n) {
        scanf("%d",&x);
        mx=max(mx,x);int up=sqrt(x);
        fo(j,1,up) 
            if (!(x%j)) {
                cnt[j]++;
                if (j*j!=x) cnt[x/j]++;
            }
    }
    fact[0]=inv[0]=mi[0]=inv[n]=1;
    fo(i,1,n) fact[i]=(ll)fact[i-1]*i%mo,mi[i]=mi[i-1]*2%mo;
    for(int y=mo-2,x=fact[n];y;y/=2,x=(ll)x*x%mo) 
        if (y&1) inv[n]=(ll)inv[n]*x%mo;
    fd(i,n-1,1) inv[i]=(ll)inv[i+1]*(i+1)%mo;
    fo(i,2,mx) {
        if (!bz[i]) p[++p[0]]=i,mu[i]=-1;
        fo(j,1,p[0]) {
            int k=i*p[j];if (k>mx) break;
            bz[k]=1;if (!(i%p[j])) break;
            mu[k]=-mu[i];
        }
    }
    mu[1]=1;
    fo(i,1,mx) if (k!=-1) g[i]=c(cnt[i],k);else g[i]=mi[cnt[i]]-1;
    fo(i,1,mx) (ans+=mu[i]*g[i])%=mo;
    printf("%d\n",(ans+mo)%mo);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值