题目链接:Click here~~
题意:
给一个序列 {an}(ai <= M),问有多少种 {bn} ,满足恰好 k 个位置与 {an} 不同,且 gcd of {bn} = d(1<= d <=M)。
解题思路:
比赛时没有什么思路,赛后看了题解才搞懂。这题的关键是要想到,将比较难解的最大公约数的问题转成公约数的问题。
令 f[d] 表示满足条件的且 gcd 为 d 的方案数,g[d] 表示满足条件的且 cd 为 d 的方案数。
有 g[d] = f[d] + f[2*d] + f[3*d] + ... ,则 f[d] = g[d] - f[2*d] - f[3*d] 。
而对于 g[d] ,我们可以先统计出 {an} 中 d 的倍数的个数,假设有 x 个。
如果 n-x > k,显然结果为 0;
如果 n-x <= k,我们先要将这 n-x 个位置变成 d 的倍数,每种位置有 M/d 种选择且相互独立,共有 (M/d) ^ (n-x) 种。同样,对于剩下的 k-(n-x) 个位置,我们需要将这些之前虽然也是 d 的倍数的变成另一个 d 的倍数,每种位置有 M/d - 1 种选择且相互独立,共有 C(x,k-(n-x)) * (M/d - 1) ^ (k-(n-x)) 种。相乘即可。
复杂度是O(m * log m)。
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; const int N = 3e5 + 5; const int mod = 1e9 + 7; int cnt[N],fac[N],fac_inv[N]; long long ans[N]; int pow_mod(int a,int n){ long long ret = 1, base = a; while(n) { if(n&1) ret = ret * base % mod; base = base * base % mod; n >>= 1; } return ret; } long long inv(long long a){ return a == 1 ? 1 : inv(mod % a) * (mod - mod/a) % mod; } int C(int n,int m){ return (long long)fac[n] * fac_inv[m] % mod * fac_inv[n-m] % mod; } int main() { fac[0] = fac_inv[0] = 1; for(int i=1;i<N;i++) { fac[i] = (long long)fac[i-1] * i % mod; fac_inv[i] = inv(fac[i]); } 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 num; scanf("%d",&num); cnt[num]++; } for(int d=m;d>=1;d--) { int sum = 0; for(int i=d;i<=m;i+=d) sum += cnt[i]; if(sum < n-k) ans[d] = 0; else { ans[d] = (long long)pow_mod(m/d,n-sum) * pow_mod(m/d-1,k-n+sum) % mod; ans[d] = ans[d] * C(sum,k-n+sum) % mod; for(int i=d+d;i<=m;i+=d) ans[d] = (ans[d] - ans[i]) % mod; ans[d] = (ans[d] + mod) % mod; } } for(int d=1;d<=m;d++) printf("%I64d%c",ans[d],d==m?'\n':' '); } return 0; }