题目链接:传送门
题意:
给定一个长度为n的序列a,且 1<=a[i]<=m,求有多少个序列b,使得GCD(b[1],b[2],...b[n])=x (1<=x<=m),且
正好有k个b[i]!=a[i].
分析:
设F[d]表示 gcd = d的满足条件的序列的个数,G[d]表示满足条件的gcd = k*d (1<=k)的序列的个数。那么很明显 F[d] = G[d] - F[2*d] -F[3*d]...
在做的时候我们先求G[d]我们枚举gcd,在原来的序列中找到gcd倍数的个数,设为tot.因为题目要求恰好与k个不同而且,gcd = d,那么新的序列肯定都得是d的倍数,原序列中不是d的倍数的个数为 n-tot.这些数十肯定需要修改的,如果
1)n-tot>k那么肯定是不可能的了。
2)n-tot<=k 那么这n-tot个数要换成d的倍数,每个数有m/d种,所有的就是 (m/d)^(n-tot),剩下的tot个数中还要选出k-(n-tot)个数变成不等于他们自己本身的d的倍数,每个数有(m/d-1)种,那么这种情况有
C(tot,k-(n-tot))*(m/d-1)^(k-(n-tot))种。G[d]=(m/d)^(n-tot)*C(tot,k-(n-tot))*(m/d-1)^(k-(n-tot))%mod
在求组合数的时候我们要先预处理一下阶乘的逆元。因为数据量比较大我们需要用递推法来预处理逆元
inv[n] = inv[mod%n]*(mod-mod/n)%mod;具体的证明:传送门
代码如下:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const LL mod = 1e9+7;
const int maxn = 3e5+10;
LL ans[maxn];
LL cnt[maxn];
LL inv[maxn];
LL fac[maxn];
LL get_inv(LL n){
if(n==1) return 1;
return get_inv(mod%n)*(mod-mod/n)%mod;
}
LL Com(LL n,LL m){
return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
void init(){
inv[0]=fac[0]=1;
for(int i=1;i<maxn;i++){
fac[i]=fac[i-1]*i%mod;
inv[i]=get_inv(fac[i]);
}
}
LL quick_mod(LL a,LL b){
LL ans = 1;
while(b){
if(b&1) ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
int main(){
init();
int n,m,k;
while(~scanf("%d%d%d",&n,&m,&k)){
memset(cnt,0,sizeof(cnt));
for(int i=0;i<n;i++){
int x;
scanf("%d",&x);
cnt[x]++;
}
for(int i=m;i>=1;i--){
LL tot = 0;
for(int j=i;j<=m;j+=i) tot+=cnt[j];
if(n-tot>k){
ans[i]=0;
continue;
}
ans[i]=quick_mod(m/i,n-tot)*quick_mod(m/i-1,k-(n-tot))%mod;
ans[i]=ans[i]*Com(tot,k-(n-tot))%mod;
for(int j=i+i;j<=m;j+=i)
ans[i]=(ans[i]-ans[j]+mod)%mod;
}
for(int i=1;i<m;i++)
printf("%I64d ",ans[i]);
printf("%I64d\n",ans[m]);
}
return 0;
}