BZOJ2226: [Spoj 5971] LCMSum

BZOJ2226: [Spoj 5971] LCMSum

Description

Given n, calculate the sum LCM(1,n) + LCM(2,n) + .. + LCM(n,n), where LCM(i,n) denotes the Least Common Multiple of the integers i and n.

Input

The first line contains T the number of test cases. Each of the next T lines contain an integer n.

Output

Output T lines, one for each test case, containing the required sum.

Sample Input

3
1
2
5

Sample Output

1
4
55

HINT

Constraints
1 <= T <= 300000
1 <= n <= 1000000


题解Here!

 

题目要求:$$Ans=\sum_{i=1}^nlcm(i,n)$$。
乍一看,很像懵逼钨丝反演,但是只有一个$\sum$。。。
那就慢慢推式子。。。
把那个$lcm$换成$gcd$:$$Ans=\sum_{i=1}^n\frac{in}{gcd(i,n)}=n\sum_{i=1}^n\frac{i}{gcd(i,n)}$$
习惯性地枚举$gcd$:$$Ans=n\sum_{d|n}\sum_{i=1}^n\frac{i}{d}[gcd(i,n)==d]$$
转变一下$i$:$$Ans=n\sum_{d|n}\sum_{i=1}^{\frac{n}{d}}i\times [gcd(i,\frac{n}{d})==1]$$
然后因为$d|n$,所以$d==\frac{n}{d}$,那么:$$Ans=n\sum_{d|n}\sum_{i=1}^di\times [gcd(i,d)==1]$$
我们要求小于$d$且与$d$互素的所有自然数之和。
根据欧拉函数的性质:小于$n$且与$n$互素的所有自然数之和为$\frac{n\times \varphi(n)}{2}$。
原式可化为:$$Ans=n\sum_{d|n}\frac{d\times \varphi(d)}{2}$$
于是直接线性筛出$\varphi(i)$,每个数暴力算到它的倍数里去,就可以预处理出答案了。
复杂度是$O(n\log_2n+T)$。
我也不知道为什么别人写的$O(n+T\sqrt{n})$的都能卡过,我只能写$O(n\log_2n+T)$的,我的常数真的这么大吗。。。
附代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#define MAXN 1000010
using namespace std;
int n;
int k=0,prime[MAXN],phi[MAXN];
long long sum[MAXN],ans[MAXN];
bool np[MAXN];
inline int read(){
	int date=0,w=1;char c=0;
	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
	return date*w;
}
void make(){
    int m=MAXN-10;
    phi[1]=1;
    for(int i=2;i<=m;i++){
        if(!np[i]){
            prime[++k]=i;
            phi[i]=i-1;
        }
        for(int j=1;j<=k&&prime[j]*i<=m;j++){
            np[prime[j]*i]=true;
            if(i%prime[j]==0){
                phi[prime[j]*i]=phi[i]*prime[j];
                break;
            }
            phi[prime[j]*i]=phi[i]*phi[prime[j]];
        }
    }
    sum[1]=1;
    for(int i=2;i<=m;i++)sum[i]=(long long)i*phi[i]/2;
    for(int i=1;i<=m;i++)
    for(int j=i;j<=m;j+=i)
    ans[j]+=sum[i];
    for(long long i=1;i<=m;i++)ans[i]*=i;
}
int main(){
    int t=read();
    make();
    while(t--){
        n=read();
        printf("%lld\n",ans[n]);
    }
    return 0;
}

 

posted @ 2018-08-14 21:35  符拉迪沃斯托克  阅读(203)  评论(0编辑  收藏  举报
Live2D