解题报告: HDU_4675 GCD of Sequence 计数

题目链接


题意:

给定长度为n的一个正整数序列,里面每个数的大小均为最大为m,让你构造出另一个等长的正整数序列要求满足一下三个条件:

1、数列里的每个数小于等于m。

2、整个数列的最大公因子为d。

3、你构造出的数列与原数列对应位置上的数刚好有k对不相等。

询问你当d 分别为 1~m 时,能构造出序列的种类数。

思路:

不好直接考虑计算最大公因子刚好为d的结果(设为f(d)),那么先计算最大公因子为d的倍数的结果(设为g(d)),那么可知,,其中u(i) 为反演系数,那么已知g函数的结果,我们就能在O(mlog(n))的时间求得答案。

条件三可以理解为从原数列中选k个数改变。

那么就有n-k个数的值不发生改变,如果这n-k个数中存在不是d的倍数的数,那么就无法得到要求的数列,此时结果为0。

设原数列中有x个数为d的倍数,那么:

① n-k > x :

 说明不变的数中一定有数不是d的倍数,此时结果为0

g[d] = 0

② n-k <= x : 

对于 (n-k) 个不是d的倍数的数 ,这些数一定要改变成小于等于m的d的倍数,它们有m/d中选择。

对于 剩下的  j = ( x - (n-k) )个原本就是d的倍数的数,这些数的选择有  C(x,j) 种,它们要变成除了它们自己以外的其余(m/d - 1)个数之一。

g[d] = pow(m/d , n-k ) * C(x,j) * pow(m/d -1 , j );


即可。

#include<bits/stdc++.h>


#define pii pair<int,int>
#define fi first
#define se second
const int mod = 1e9+7;
using namespace std;

int n,m,k;
int A[300005];
int mu[300005];
int pr[33000];
long long ni[300005];
long long fac[300006];
int isp[300005];
int num[300005];
long long ans[300005];
vector<pii>V;
vector<int>E[300005];
long long qpow(long long x,int y){
    long long res = 1;
    while(y){
        if(y&1)res = res * x % mod;
        y>>=1;
        x = x * x % mod ;
    }return res;
}

void init(){
    fac[0] = ni[0] = 1;
    for(int i=1;i<=3e5;i++){
        isp[i]=true;
        fac[i] =(1LL*fac[i-1]*i%mod);
        ni[i] = qpow(fac[i],mod-2);
        for(int j=i;j<=3e5;j+=i){
            E[j].emplace_back(i);
        }
    }
    mu[1]=1;
    int all = 0;
    for(int i=2;i<=3e5;i++){
        if(isp[i]){
            mu[i] = -1;
            pr[all++] = i;
        }for(int j=0;j<all;j++){
            long long t = 1LL * pr[j] * i;
            if(t>3e5)break;
            mu[t] = -mu[i];
            isp[t] = false;
            if(i%pr[j]==0){
                mu[t] = 0;
                break;
            }
        }if(mu[i]){
            V.emplace_back(pii(i,mu[i]));
        }
    }
}


inline long long C(int n,int m){
    if(n==m||m==0)return 1LL;
    return 1LL*fac[n]*ni[n-m]%mod*ni[m]%mod;
}



int main()
{
    init();
    while(scanf("%d%d%d",&n,&m,&k)==3){
        memset(num,0,sizeof(num));
        for(int i=1;i<=n;i++){
            scanf("%d",&A[i]);
            for(int j=0;j<E[A[i]].size();j++){
                num[E[A[i]][j]]++;
            }
        }for(int i=1;i<=m;i++){
            int cnt = m/i , j = k+num[i]-n;
            if(n-num[i]<=k)ans[i] = 1LL * qpow(cnt,n-num[i]) * C(num[i],j) % mod * qpow(cnt-1,j) % mod;
            else ans[i] = 0;
        }for(int i=1;i<=m;i++){
            for(int j=0;j<V.size();j++){
                int t = V[j].fi * i ;
                if(t<=m){
                    ans[i] = ( ans[i] +  V[j].se * ans[t] + mod )% mod ;
                }else {
                    break;
                }
            }printf("%I64d%c",ans[i],i<m?' ':'\n');
        }
    }return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值