传送门:https://nanti.jisuanke.com/t/36108
本题出自计蒜客模拟赛。
比较容易猜的一个结论就是,题目中所说的势能,就是S除这个子集的gcd。
原因先看两个元素的集合,设为{a,b},
势
能
=
l
c
m
(
S
a
,
S
b
)
=
S
2
a
b
∗
g
c
d
(
S
a
,
S
b
)
势能=lcm(\frac{S}{a},\frac{S}{b})=\frac{S^2}{ab*gcd(\frac{S}{a},\frac{S}{b})}
势能=lcm(aS,bS)=ab∗gcd(aS,bS)S2,用德摩根公式或者画图法可以知道
g
c
d
(
S
a
,
S
b
)
=
S
l
c
m
(
a
,
b
)
gcd(\frac{S}{a},\frac{S}{b})=\frac{S}{lcm(a,b)}
gcd(aS,bS)=lcm(a,b)S,于是
势
能
=
S
∗
l
c
m
(
a
,
b
)
a
∗
b
=
S
g
c
d
(
a
,
b
)
势能=\frac{S*lcm(a,b)}{a*b}=\frac{S}{gcd(a,b)}
势能=a∗bS∗lcm(a,b)=gcd(a,b)S。两个元素证明之后,第三个或者更多的元素情况可以采用归纳法证明。
问题简化了不少,我们发现需要求的是gcd为某一个数的子集个数,所以要先求出gcd为这个数倍数的子集个数,再使用容斥原理或者莫比乌斯反演的方法。两种效率都差不多,但由于内存卡得有点紧,筛法储存1~1E6的每个因子会爆内存,所以只好采用莫比乌斯反演法。详细步骤见代码。
#include<cstdio>
#include<forward_list>
#define mo 1000000007
using namespace std;
using LL=long long;
int n,pi,h[1000005],ans[1000005],res,p2[1000005],inv[1000005];
int p[72400],pn,miu[1000005];
bool f[1000005];
void sieve()
{
miu[1]=1;
p[0]=1E9;
for(int i=2,j,k;i<=1E6;i++)
{
if(!f[i])
p[++pn]=i,miu[i]=-1;
for(j=1;j<=pn&&(k=p[j]*i)<=1E6&&i%p[j-1];j++)
f[k]=true,miu[k]=-miu[i];
if(i%p[j-1]==0)
miu[p[j-1]*i]=0;
}
}
int main()
{
sieve();
scanf("%d",&n);
pi=1;
for(int i=1,a;i<=n;i++)
scanf("%d",&a),pi=(LL)pi*a%mo,h[a]++;
p2[0]=1;
inv[1]=1;
for(int i=2;i<=1E6;i++)
inv[i]=(LL)inv[mo%i]*(mo-mo/i)%mo; //最近学到的一个小技巧,求连续数的逆元的时候可以采用递推的方法O(n)来做
for(int i=1;i<=1E6;i++)
p2[i]=p2[i-1]*2%mo;
for(int i=1,cnt;i<=1E6;i++)
{
cnt=0;
for(int j=i;j<=1E6;j+=i)
cnt+=h[j];
ans[i]=(p2[cnt]-1+mo)%mo;
}
for(int i=1,lim;i<=1E6;i++)
{
lim=1E6/i;
for(int j=2;j<=lim;j++)
ans[i]=((LL)ans[i]+ans[j*i]*miu[j]+mo)%mo;
res=(res+(LL)ans[i]*pi%mo*inv[i])%mo;
}
printf("%d",res);
return 0;
}