51nod 1375 再选数 容斥原理+线性筛

博客介绍了如何运用容斥原理和线性筛算法解决一类问题:从n个正整数中选择k个数,使得它们的最大公约数为1的方案数。当k=-1时,需要至少选一个数。通过枚举素因子并计算组合数,求得符合条件的方案总数,答案对998,244,353取模。" 126640080,12931599,Java 8 Lambda 表达式详解,"['java', '开发语言', 'jvm', '函数式编程']
摘要由CSDN通过智能技术生成

题意

从前有n个正整数,我们令它为a[1]到a[n]。现在要从选出恰好k个数(如果k=-1则为选任意个数,但是至少选一个),要求这些数的最大公约数是1,问有多少种方案?
答案可能很大,模998,244,353输出。
1≤n≤100,000;1≤k≤n或k=-1。所有的1≤a[i]≤1,000,000并且是随机生成的。

分析

考虑容斥,用全部方案减去gcd大于1的方案。可以枚举gcd有哪些素因子,设这些素因子的乘积为d,那么容斥系数显然就是 μ(d) ,然后统计一下d的倍数的数量,用组合数算一算即可。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;

const int N=1000005;
const int MOD=998244353;

int n,m,t[N],prime[N],tot,mu[N],jc[N],ny[N],po[N],k;
bool not_prime[N];

int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

void get_prime(int n)
{
    mu[1]=1;
    for (int i=2;i<=n;i++)
    {
        if (!not_prime[i]) prime[++tot]=i,mu[i]=-1;
        for (int j=1;j<=tot&&i*prime[j]<=n;j++)
        {
            not_prime[i*prime[j]]=1;
            if (i%prime[j]==0) break;
            mu[i*prime[j]]=-mu[i];
        }
    }
}

int get_c(int n,int m)
{
    if (n<m) return 0;
    return (LL)jc[n]*ny[m]%MOD*ny[n-m]%MOD;
}

int main()
{
    n=read();k=read();int mx=0;
    for (int i=1,x;i<=n;i++) x=read(),mx=max(mx,x),t[x]++;
    get_prime(mx);
    jc[0]=jc[1]=ny[0]=ny[1]=po[0]=1;po[1]=2;
    for (int i=2;i<=n;i++) jc[i]=(LL)jc[i-1]*i%MOD,ny[i]=(LL)(MOD-MOD/i)*ny[MOD%i]%MOD;
    for (int i=2;i<=n;i++) ny[i]=(LL)ny[i-1]*ny[i]%MOD,po[i]=po[i-1]*2,po[i]-=po[i]>=MOD?MOD:0;
    int ans=k==-1?po[n]-1:get_c(n,k);
    for (int d=2;d<=mx;d++)
    {
        if (!mu[d]) continue;
        int s=0;
        for (int i=d;i<=mx;i+=d) s+=t[i];
        if (k>0) ans+=mu[d]>0?get_c(s,k):MOD-get_c(s,k);
        else ans+=mu[d]>0?po[s]-1:MOD-po[s]+1;
        ans-=ans>=MOD?MOD:0;
    }
    printf("%d",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值