链接:http://acm.hdu.edu.cn/showproblem.php?pid=6053
定义g(d)为gcd(i,j)%d==0的数量,对于一个位置a[x]有a[x]/d(向下取整)种,那么g(d)= ∏ ( a [ x ] / d ) \prod(a[x]/d) ∏(a[x]/d),d的范围是[2,min(a)]
而最后的答案应该是g(2)+g(3)+0*g(4)+g(5)-g(6)+g(7)+0*g(8)+0*g(9)…
为什么4的系数是0呢?因为g(4)在g(2)已经算了,g(8)g(9)都是同理。
为什么g(6)的系数是-1?因为g(2)中算过一次,g(3)中也算过一次,那么就多了一次,所以要减一次。
这真好满足了mu函数的规律,只是符号正好反了,所以ans= ∑ \sum ∑d=2:min(a) − m u [ d ] -mu[d] −mu[d] ∏ \prod ∏i=1:n ( a [ i ] / d ) (a[i]/d) (a[i]/d)
直接做的复杂度min(a)*n,是1e10级的,显然不可以。
考虑如何优化:
换一种存法,把原来a数组的值当作下标,记录一下那个值出现了多少次,然后做一个前缀和。
这样公式就变成了ans= ∑ \sum ∑d=2:min(a) − m u [ d ] -mu[d] −mu[d]* ∏ i = 1 m a x ( a ) / d i 在 d 下 的 次 数 \prod_{i=1}^{max(a)/d}i^{在d下的次数} ∏i=1max(a)/di在d下的次数
在d下的次数的意思是原a数组中a[x]/d==i的个数。枚举的时候,当枚举到d和i时,容易反推出[di,d(i+1)-1]这个区间里面的任意一个值向下整除d都等于i,所以这个区间的个数和就是i的次数了,注意区间的右端点不能超出原a数组中最大值。前缀和的意义也就是为了O1地获得这个次数,对于次数容易想到使用快速幂来优化。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
const int maxn=1e5;
const ll mod=1e9+7;
int mu[N],prime[N];
bool vis[N];
void work_mobius()
{
mu[1]=1;
int cnt=0;
for(int i=2;i<=maxn;i++)
{
if(!vis[i])
{
prime[cnt++]=i;
mu[i]=-1;
}
for(int j=0;j<cnt&&i*prime[j]<=maxn;j++)
{
vis[i*prime[j]]=true;
if(i%prime[j]) mu[i*prime[j]]=-mu[i];
else
{
mu[i*prime[j]]=0;
break;
}
}
}
}
ll a[N];
ll qpow(ll a,ll b){
ll ret=1;
while(b){
if(b&1)ret=ret*a%mod;
a=a*a%mod;
b>>=1;
}
return ret;
}
int main(){
work_mobius();
int t;
scanf("%d",&t);
for(int ca=1;ca<=t;ca++){
memset(a,0,sizeof(a));
int n;
scanf("%d",&n);
ll ma=0,mi=N;
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
ma=max(ma,1ll*x);
mi=min(mi,1ll*x);
a[x]++;
}
for(int i=2;i<=ma;i++)a[i]=a[i-1]+a[i];
ll ans=0;
for(int i=2;i<=mi;i++){//感性地体会下当d越大,内层的循环次数越少
if(mu[i]==0)continue;//mu[i]==0的话,贡献肯定时0,没必要算,减少了很多运算
ll mm=1ll*-mu[i];
int ed=ma/i;
for(int j=1;j<=ed;j++){
ll l=i*j,r=min(ma,1ll*(i*j+i-1));
mm=mm*qpow(1ll*j,a[r]-a[l-1])%mod;
}
ans=(ans+mm+mod)%mod;
}
printf("Case #%d: %lld\n",ca,ans);
}
return 0;
}