Problem
给出 n n n 个数,计算其所有子集的 G C D GCD GCD(即子集所有数的 g c d gcd gcd)的乘积对 1 0 9 + 7 10^9+7 109+7 取模的结果。
其中空子集的 G C D GCD GCD 为 1 1 1。
数据范围: 2 ≤ n , a i ≤ 1 0 5 2≤n,a_i≤10^5 2≤n,ai≤105。
Solution
考场上一个细节没注意,硬生生把 100 100 100 分变成了 20 20 20 分。。。
先用桶排,算出每个数的出现次数。然后考虑枚举 g c d gcd gcd,假设当前枚举的数为 x x x,可以求出 x x x 的倍数个数 s u m x sum_x sumx,那么初步认为以 x x x 为 g c d gcd gcd 的集合数 s e t x = ( 2 s u m x − 1 ) set_x=(2^{sum_x}-1) setx=(2sumx−1) 个。
但是这样显然是不对的,举个例子,在枚举 2 2 2 的时候,这样会计算到 { 6 } , { 4 , 8 } \{6\},\{4,8\} {6},{4,8} 等集合。要想办法除去这些集合。
由于多算的集合的 g c d gcd gcd 肯定是 x x x 的倍数,因此我们枚举 x x x 的倍数然后减掉相应的 s e t set set 值即可,即以 x x x 为 g c d gcd gcd 的子集数量为 s e t x − ∑ n ∣ d s e t d set_x-\sum_{n|d}set_d setx−∑n∣dsetd。
由于我们要先知道所有倍数的 s e t set set 值才能推到 x x x,故应倒着循环。
最后的答案 a n s = ∏ i = 1 1 0 5 i s e t i ans=\prod_{i=1}^{10^5}i^{set_i} ans=∏i=1105iseti,时间复杂度 O ( n log n ) O(n\log n) O(nlogn)。
有一个地方要注意,即 s e t i set_i seti 可能比较大,我们要先对它进行降幂操作。根据费马小定律,当 p p p 为质数时, a p − 1 ≡ 1 (   m o d    p ) a^{p-1}\equiv 1(\,\mathrm{mod}\;p) ap−1≡1(modp),因此要将 s e t i % ( p − 1 ) set_i\%(p-1) seti%(p−1) 再用快速幂。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define Mod 1000000007
using namespace std;
int cnt[N],Set[N];
int Power(int a,int b,int p){
int ans=1;
for(;b;b>>=1,a=1ll*a*a%p)
if(b&1) ans=1ll*ans*a%p;
return ans;
}
int main(){
int n,x,ans=1;
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&x),cnt[x]++;
for(int i=2;i<=1e5;++i){
int sum=0;
for(int j=i;j<=1e5;j+=i) sum+=cnt[j];
Set[i]=Power(2,sum,Mod-1)-1;
}
for(int i=1e5;i>=2;--i){
if(!Set[i]) continue;
for(int j=2*i;j<=1e5;j+=i) Set[i]=(Set[i]-Set[j]+Mod-1)%(Mod-1);
ans=1ll*ans*Power(i,Set[i],Mod)%Mod;
}
printf("%d",ans);
return 0;
}