- 题目链接
- 题意: 给出一个数N,输出小于等于N的所有数,两两之间的最大公约数之和。即计算 G = ∑ i = 1 i < n ∑ j = i + 1 j < = i G C D ( i , j ) G=\sum_{i=1}^{i<n}\sum_{j=i+1}^{j<=i}GCD(i,j) G=∑i=1i<n∑j=i+1j<=iGCD(i,j)
- 输入:
第1行:1个数T,表示后面用作输入测试的数的数量。(1 <= T <= 50000)
第2 - T + 1行:每行一个数N。(2 <= N <= 5000000)
- 输出:
共T行,输出最大公约数之和。
- 输入样例:
3
10
100
200000
- 输出样例:
67
13015
143295493160
- 题解:
首先可以将计算公式转化成
∑ i = 2 n ∑ j = 1 i − 1 g c d ( i , j ) \sum_{i=2}^{n}\sum_{j=1}^{i-1}gcd(i,j) ∑i=2n∑j=1i−1gcd(i,j)
= ∑ i = 2 n ( ∑ j = 1 i g c d ( i , j ) − i ) =\sum_{i=2}^{n}(\sum_{j=1}^{i}gcd(i,j)-i) =∑i=2n(∑j=1igcd(i,j)−i)
= ∑ i = 2 n ∑ j = 1 i g c d ( i , j ) − ∑ i = 2 n i =\sum_{i=2}^{n}\sum_{j=1}^{i}gcd(i,j)-\sum_{i=2}^{n}i =∑i=2n∑j=1igcd(i,j)−∑i=2ni
= ∑ i = 1 n ∑ j = 1 i g c d ( i , j ) − ∑ i = 1 n i =\sum_{i=1}^{n}\sum_{j=1}^{i}gcd(i,j)-\sum_{i=1}^{n}i =∑i=1n∑j=1igcd(i,j)−∑i=1ni
现在只需要计算前面一部分即可
∑ i = 1 n ∑ j = 1 i g c d ( i , j ) \sum_{i=1}^{n}\sum_{j=1}^{i}gcd(i,j) ∑i=1n∑j=1igcd(i,j)
= ∑ d = 1 n d ∑ i = 1 n ∑ j = 1 i [ g c d ( i , j ) = d ] =\sum_{d=1}^{n}d\sum_{i=1}^{n}\sum_{j=1}^{i}[gcd(i,j)=d] =∑d=1nd∑i=1n∑j=1i[gcd(i,j)=d]
= ∑ d = 1 n d ∑ i = 1 ⌊ n d ⌋ ∑ j = 1 i [ g c d ( i , j ) = 1 ] =\sum_{d=1}^{n}d\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{i}[gcd(i,j)=1] =∑d=1nd∑i=1⌊dn⌋∑j=1i[gcd(i,j)=1]
= ∑ d = 1 n d ∑ i = 1 ⌊ n d ⌋ φ ( i ) =\sum_{d=1}^{n}d\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\varphi(i) =∑d=1nd∑i=1⌊dn⌋φ(i)
然后解可以分块求解,只要先预处理出欧拉函数的前缀和即可。复杂度是
O
(
T
N
)
O(T\sqrt{N})
O(TN),看一下数据范围貌似可以通过。
然而一切并没有那么美好,这样写是会让你T到怀疑人生的
换一种方法分析,对于
∑
i
=
1
n
∑
j
=
1
i
g
c
d
(
i
,
j
)
\sum_{i=1}^{n}\sum_{j=1}^{i}gcd(i,j)
∑i=1n∑j=1igcd(i,j)
我们先来分析里面的式子,
∑
j
=
1
i
g
c
d
(
i
,
j
)
\sum_{j=1}^{i}gcd(i,j)
∑j=1igcd(i,j),我们枚举
g
c
d
(
i
,
j
)
gcd(i,j)
gcd(i,j),因为他是i的约数,则
=
∑
d
∣
i
d
∑
j
=
1
i
[
g
c
d
(
i
,
j
)
=
d
]
=\sum_{d\mid i}d\sum_{j=1}^{i}[gcd(i,j)=d]
=∑d∣id∑j=1i[gcd(i,j)=d]
=
∑
d
∣
i
d
∑
j
=
1
⌊
i
d
⌋
[
g
c
d
(
i
,
j
)
=
1
]
=\sum_{d\mid i}d\sum_{j=1}^{\lfloor\frac{i}{d}\rfloor}[gcd(i,j)=1]
=∑d∣id∑j=1⌊di⌋[gcd(i,j)=1]
=
∑
d
∣
i
d
φ
(
⌊
i
d
⌋
)
=\sum_{d\mid i}d\varphi(\lfloor\frac{i}{d}\rfloor)
=∑d∣idφ(⌊di⌋)
令
f
(
n
)
=
∑
d
∣
n
d
φ
(
⌊
n
d
⌋
)
f(n)=\sum_{d\mid n}d\varphi(\lfloor\frac{n}{d}\rfloor)
f(n)=∑d∣ndφ(⌊dn⌋) ,
f
=
φ
∗
I
d
f=\varphi*Id
f=φ∗Id,因为函数
φ
\varphi
φ与
I
d
Id
Id都是积性函数,二者作巻积得到的函数仍然是积性函数,因此函数
f
f
f也是积性函数,因此可以通过积性函数线性
O
(
N
)
O(N)
O(N)求得(
N
⩽
5
e
6
N\leqslant5e6
N⩽5e6)。所以
G
=
∑
i
=
2
n
f
(
i
)
−
∑
i
=
2
n
i
G=\sum_{i=2}^{n}f(i)-\sum_{i=2}^{n}i
G=∑i=2nf(i)−∑i=2ni
如何计算
f
(
n
)
=
∑
d
∣
n
d
φ
(
⌊
n
d
⌋
)
f(n)=\sum_{d\mid n}d\varphi(\lfloor\frac{n}{d}\rfloor)
f(n)=∑d∣ndφ(⌊dn⌋)
当
n
=
p
n=p
n=p时,
f
(
p
)
=
1
∗
φ
(
p
)
+
p
∗
φ
(
1
)
=
2
∗
p
−
1
f(p)=1*\varphi(p)+p*\varphi(1)=2*p-1
f(p)=1∗φ(p)+p∗φ(1)=2∗p−1
当
n
=
p
k
n=p^{k}
n=pk时,
f
(
p
k
)
=
1
∗
φ
(
p
k
)
+
p
∗
φ
(
p
k
−
1
)
+
.
.
.
+
p
k
−
1
φ
(
p
)
+
p
k
φ
(
1
)
f(p^{k})=1*\varphi(p^{k})+p*\varphi(p^{k-1})+...+p^{k-1}\varphi(p)+p^{k}\varphi(1)
f(pk)=1∗φ(pk)+p∗φ(pk−1)+...+pk−1φ(p)+pkφ(1)
=
p
k
−
1
∗
(
p
−
1
)
+
p
∗
p
k
−
2
∗
(
p
−
1
)
+
.
.
.
+
p
k
−
1
∗
(
p
−
1
)
+
p
k
=p^{k-1}*(p-1)+p*p^{k-2}*(p-1)+...+p^{k-1}*(p-1)+p^{k}
=pk−1∗(p−1)+p∗pk−2∗(p−1)+...+pk−1∗(p−1)+pk
=
k
∗
p
k
−
1
∗
(
p
−
1
)
+
p
k
=k*p^{k-1}*(p-1)+p^{k}
=k∗pk−1∗(p−1)+pk
=
(
k
+
1
)
∗
p
k
−
k
∗
p
k
−
1
=(k+1)*p^{k}-k*p^{k-1}
=(k+1)∗pk−k∗pk−1
当
n
=
p
k
−
1
n=p^{k-1}
n=pk−1,时
f
(
p
k
−
1
)
=
k
∗
p
k
−
1
−
(
k
−
1
)
∗
p
k
−
2
f(p^{k-1})=k*p^{k-1}-(k-1)*p^{k-2}
f(pk−1)=k∗pk−1−(k−1)∗pk−2
f
(
p
k
)
=
p
∗
f
(
p
k
−
1
)
+
p
k
−
1
∗
(
p
−
1
)
f(p^{k})=p*f(p^{k-1})+p^{k-1}*(p-1)
f(pk)=p∗f(pk−1)+pk−1∗(p−1)
由此就可以通过积性函数线性筛
O
(
N
)
O(N)
O(N)计算出函数
f
f
f。再处理出其前缀和,就可以O(1)的返回查询的每一个
n
n
n。
- 代码实现:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define mes(a, val) memset(a, val, sizeof a)
#define mec(b, a) memcpy(b, a, sizeof a)
const int maxn = 5e6+ 100;
bool v[maxn];
int p[maxn];
ll f[maxn];
int low[maxn];
int m;
int read(){
char x;
while((x = getchar()) > '9' || x < '0') ;
int u = x - '0';
while((x = getchar()) <= '9' && x >= '0')
u = (u << 3) + (u << 1) + x - '0';
return u;
}
void sieze(int n)
{
m = 0; mes(v, false);
for(int i = 2; i <= n; i ++){
if(!v[i]){
p[++ m] = i; low[i] = i; f[i] = 2 * i - 1;
}
for(int j = 1; j <= m && i * p[j] <= n; j ++){
v[i * p[j]] = true;
if(i % p[j]){
f[i * p[j]] = f[i] * f[p[j]];
low[i * p[j]] = p[j];
}
else {
if(low[i] == i) f[i * p[j]] = p[j] * f[i] + i * (p[j] - 1);
else f[i * p[j]] = f[i / low[i]] * f[low[i] * p[j]];
low[i * p[j]] = low[i] * p[j];
break;
}
}
}
f[1] = 0;
for(int i = 2; i <= n; i ++){
f[i] += f[i-1];
}
}
ll solve(ll n)
{
ll ans = f[n] - (n + 2) * (n - 1) / 2;
return ans;
}
int main()
{
sieze(maxn - 10);
int T; scanf("%d", &T);
while(T --){
ll n; n = read();
ll ans = solve(n);
printf("%lld\n", ans);
}
return 0;
}