[bzoj4305]数列的gcd

本文探讨了一道关于数列最大公约数的问题,通过预处理和莫比乌斯反演来计算特定条件下不同数列的数量,并给出了完整的C++实现代码。

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

题目描述

给出一个长度为N的数列{a[n]},1<=a[i]<=M(1<=i<=N)。
现在问题是,对于1到M的每个整数d,有多少个不同的数列b[1], b[2], …, b[N],满足:
(1)1<=b[i]<=M(1<=i<=N);
(2)gcd(b[1], b[2], …, b[N])=d;
(3)恰好有K个位置i使得a[i]<>bi
注:gcd(x1,x2,…,xn)为x1, x2, …, xn的最大公约数。
输出答案对1,000,000,007取模的值。

瞎做

我设f[i]表示d=i的答案
用一个g[i]表示所有i|d的d的答案和。
然后莫比乌斯反演一波。
所以我们只需要算g。
可以通过预处理求出不是d的倍数有t个。
那么这个t个一定不和原来相同
剩下n-t个中要有k-t个和原来相同
就是一个组合数咯

#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int maxn=300000+10,mo=1000000007;
int mu[maxn],cnt[maxn],b[maxn],pri[maxn],f[maxn],g[maxn],fac[maxn],inv[maxn],a[maxn];
bool bz[maxn];
int i,j,k,l,t,n,m,top;
int quicksortmi(int x,int y){
    if (!y) return 1;
    int t=quicksortmi(x,y/2);
    t=(ll)t*t%mo;
    if (y%2) t=(ll)t*x%mo;
    return t;
}
int C(int n,int m){
    if (n<m||n<0||m<0) return 0;
    return (ll)fac[n]*inv[m]%mo*inv[n-m]%mo;
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    fo(i,1,n){
        scanf("%d",&a[i]);
        b[a[i]]++;
    }
    fo(i,1,m)
        fo(j,1,m/i)
            cnt[i]+=b[i*j];
    fac[0]=1;
    fo(i,1,n) fac[i]=(ll)fac[i-1]*i%mo;
    inv[n]=quicksortmi(fac[n],mo-2);
    fd(i,n-1,0) inv[i]=(ll)inv[i+1]*(i+1)%mo;
    top=0;
    mu[1]=1;
    fo(i,2,m){
        if (!bz[i]){
            pri[++top]=i;
            mu[i]=-1;
        }
        fo(j,1,top){
            if ((ll)i*pri[j]>m) break;
            bz[i*pri[j]]=1;
            if (i%pri[j]==0){
                mu[i*pri[j]]=0;
                break;
            }
            mu[i*pri[j]]=-mu[i];
        }
    }
    fo(i,1,m){
        t=n-cnt[i];
        if (k<t) continue;
        g[i]=(ll)C(n-t,k-t)*quicksortmi(m/i-1,k-t)%mo*quicksortmi(m/i,t)%mo;
    }
    fo(i,1,m){
        fo(j,1,m/i)
            f[i]=(f[i]+g[i*j]*mu[j])%mo;
        (f[i]+=mo)%=mo;
        printf("%d%c",f[i],i==m?'\n':' ');
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值