(有任何问题欢迎留言或私聊 && 欢迎交流讨论哦
保证所有人都能看懂,保证你只要会筛欧拉函数就能AC。
Catalog
Problem:传送门
Portal
原题目描述在最下面。
T
(
300000
)
T(300000)
T(300000)组数据,每次求
n
(
1000000
)
n(1000000)
n(1000000)以内所有数与
n
n
n的
l
c
m
lcm
lcm之和。
Solution:
这种数据,对于我这种大常数玩家来说是真的不友好,太难受了,想卡卡不过去。。。
回归正题:
a
n
s
=
∑
i
=
1
n
L
C
M
(
i
,
n
)
=
∑
i
=
1
n
i
×
n
g
c
d
(
i
,
n
)
=
n
×
∑
i
=
1
n
i
g
c
d
(
i
,
n
)
ans = \sum_{i=1}^{n} LCM(i,n)= \sum_{i=1}^{n}\frac {i\times n}{gcd(i,n)}=n\times \sum_{i=1}^{n}\frac {i}{gcd(i,n)}
ans=∑i=1nLCM(i,n)=∑i=1ngcd(i,n)i×n=n×∑i=1ngcd(i,n)i
首先
g
c
d
(
i
,
n
)
gcd(i,n)
gcd(i,n)的取值一定会取遍
n
n
n的所有因子,然后我们把所有和
n
n
n的
g
c
d
gcd
gcd相同的数字一起求和,就会得到下面这个式子:
n
×
∑
d
∣
n
∑
i
=
1
n
i
×
[
g
c
d
(
i
,
n
)
=
=
d
]
d
n\times \sum_{d|n} \frac{\sum_{i=1}^{n}i\times [gcd(i,n) == d]}{d}
n×d∣n∑d∑i=1ni×[gcd(i,n)==d]
再来看这个,若
g
c
d
(
i
,
n
)
=
d
gcd(i,n)=d
gcd(i,n)=d,那么一定有
g
c
d
(
i
d
,
n
d
)
=
1
gcd(\frac{i}{d},\frac{n}{d})=1
gcd(di,dn)=1。
n
n
n以内和
n
n
n的
g
c
d
gcd
gcd为
d
d
d的数一定是:
d
,
d
×
2
,
.
.
.
,
d
×
n
d
d,d\times 2,...,d\times \frac{n}{d}
d,d×2,...,d×dn。有
g
c
d
(
d
×
x
,
n
)
=
d
,
    
x
≤
n
d
gcd(d\times x, n) = d,\;\;x\leq \frac{n}{d}
gcd(d×x,n)=d,x≤dn,由这个可以等价地化简为
g
c
d
(
x
,
n
d
)
=
1
    
x
≤
n
d
gcd(x,\frac{n}{d})=1\;\;x\leq \frac{n}{d}
gcd(x,dn)=1x≤dn,然后可以进一步化简答案为:
n
×
∑
d
∣
n
∑
i
=
1
n
d
i
×
[
g
c
d
(
i
,
n
d
)
=
=
1
]
n\times \sum_{d|n} \sum_{i=1}^{\frac{n}{d}}i\times [gcd(i,\frac{n}{d}) == 1]
n×d∣n∑i=1∑dni×[gcd(i,dn)==1]
再看,第一个求和里面的式子不就是
x
x
x以内所有与
x
x
x互质的数之和吗?答案更加简单了。定义
n
n
n与其互质数之和为
f
(
n
)
f(n)
f(n)。
n
×
∑
d
∣
n
∑
i
=
1
d
i
×
[
g
c
d
(
i
,
d
)
=
=
1
]
=
n
×
∑
d
∣
n
f
(
d
)
n\times \sum_{d|n} \sum_{i=1}^{d}i\times [gcd(i,d) == 1]=n\times \sum_{d|n}f(d)
n×d∣n∑i=1∑di×[gcd(i,d)==1]=n×d∣n∑f(d)
通过:
g
c
d
(
n
,
m
)
=
1
,
那
么
g
c
d
(
n
,
n
−
m
)
=
1
gcd(n,m)=1,那么gcd(n,n-m)=1
gcd(n,m)=1,那么gcd(n,n−m)=1,我们可以很轻松地得到下面公式:
当
n
=
1
n=1
n=1时,
f
(
n
)
=
1
f(n)=1
f(n)=1;当
n
≥
2
n\geq 2
n≥2时,
f
(
n
)
=
n
×
ϕ
(
n
)
2
f(n)=\frac{n\times \phi(n)}{2}
f(n)=2n×ϕ(n)。
ϕ
(
n
)
是
n
的
欧
拉
函
数
的
值
\phi(n)是n的欧拉函数的值
ϕ(n)是n的欧拉函数的值
我们可以很轻松每次以
O
(
n
)
O(\sqrt n)
O(n)算出答案,但是很抱歉这样刚刚好超时了,当然如果你的常数很小,说不定可以过。。。。。那我们就只能通过预处理来解决问题了。
有两种计数方法:第一个
f
o
r
for
for枚举各种因子,第二个
f
o
r
for
for枚举它的倍数;第一个
f
o
r
for
for枚举所有
≤
(
M
A
X
)
\leq \sqrt(MAX)
≤(MAX)的因子,第二个
f
o
r
for
for枚举另一个因子。
- 法1:
for(int i = 1; i <= N; ++i) {//i是因子
for(int j = i; j <= N; j += i) {//显然这里i是j的因子,直接给dp[j]累加上f(i)*j即可
if(i == 1) dp[j] += j;
else dp[j] += i * phi[i] / 2 * j;
}
}
- 法2:
//计算i的因子是1和i本身的情况
for(int i = 1; i <= N; ++i) dp[i] = i + i * phi[i] / 2 * i;
//i是第一个因子,j是第二个因子
for(int i = 2; i * i <= N; ++i) {
dp[i*i] += i * phi[i] / 2 * i * i;//对于完全平方数的情况,累加上f(i)*(i*i)
for(int j = i + 1; j * i <= N; ++j) {//对于其他的组合情况,累加上[f(i)+f(j)]*(i*j)
dp[i*j] += (i * phi[i] / 2 + j * phi[j] / 2 ) * i * j;
}
}
好了,这个题就解决了,也没什么难理解的吧。。。
写这个题题解的原因了,就是去年寒训的时候,被这种题虐哭了,,,今年给新生爽爽。。嘻嘻,然后想写一篇最易理解的题解给去年的自己看。
AC_Code:
#include<bits/stdc++.h>
namespace lh {
#define o2(x) (x)*(x)
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<LL, LL> pii;
}
using namespace lh;
const int MXN = 1e6 + 7;
const int INF = 0x3f3f3f3f;
const int MOD = 1000000007;
const int mod = 2019;
int x;
int pp[MXN], noprime[MXN], pcnt;
LL phi[MXN];
void get_prime() {
noprime[0] = noprime[1] = 1;
phi[1] = 1;
for(int i = 2; i < MXN; ++i) {
if(!noprime[i]) pp[pcnt++] = i, phi[i] = i-1;
for(int j = 0; j < pcnt && pp[j] * i < MXN; ++j) {
noprime[pp[j] * i] = 1;
if(i % pp[j] == 0) {
phi[i * pp[j]] = phi[i] * pp[j];
break;
}
phi[i * pp[j]] = phi[i] * (pp[j]-1);
}
}
}
LL dp[MXN];
/*
n >= 2时,n以内与其互质数之和为n*phi(n)/2
暴力点,nsqrt(n)暴力所有情况
可以第一个for枚举各种因子,第二个for枚举它的倍数
或者
第一维枚举所有小于等于sqrt(MAX)的因子,第二维枚举另一个因子
*/
void init1(int N) {
for(int i = 1; i <= N; ++i) {
for(int j = i; j <= N; j += i) {
if(i == 1) dp[j] += j;
else dp[j] += i * phi[i] / 2 * j;
}
}
}
void init2(int N) {
for(int i = 1; i <= N; ++i) dp[i] = i + i * phi[i] / 2 * i;
for(int i = 2; i * i <= N; ++i) {
dp[i*i] += i * phi[i] / 2 * i * i;
for(int j = i + 1; j * i <= N; ++j) {
dp[i*j] += (i * phi[i] / 2 + j * phi[j] / 2 ) * i * j;
}
}
}
int main() {
get_prime();
init1(1000001);//init2(1000001);
int tim; scanf("%d", &tim);
while(tim --) {
scanf("%d", &x);
printf("%lld\n", dp[x]);
}
return 0;
}