数论函数练习记录(一)

数论函数练习记录(一)

参考资料:oi-wiki 莫比乌斯反演oi-wiki 筛法oi-wiki 欧拉函数blogblog

[CQOI2007]余数求和

题目链接

这道题主要是练习数论分块

减号后面的值进行数论分块,每一块是常数 $\left\lfloor\frac{k}{l}\right\rfloor$ 乘以一个等差数列求和 $l+\cdots+r=\frac{(l+r)(r-l+1)}{2}$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<algorithm>
#include<cstdio>

using namespace std;

long long n, k, ans;

int main(){
scanf("%lld%lld", &n, &k);
ans = 1ll * n * k;
for(long long l = 1, r; l <= n; l = r + 1){
if(k / l == 0) r = n;
else r = min(n, k / (k / l));
ans -= (k / l) * (l + r) * (r - l + 1) / 2;
}
printf("%lld\n", ans);
return 0;
}

[POI2007]ZAP-Queries

题目链接

接下来进行数论分块,每一块是常数 $\left\lfloor\frac{n}{kl}\right\rfloor\left\lfloor\frac{m}{kl}\right\rfloor$ 乘上 $\mu(l)+\cdots+\mu(r)$,前缀和预处理即可。

这里 $r$ 的计算:$\left\lfloor\frac{n}{kl}\right\rfloor=\left\lfloor\frac{n}{kr}\right\rfloor\leqslant\frac{n}{kr}\implies r\leqslant\frac{\frac{n}{k}}{\left\lfloor\frac{n}{kl}\right\rfloor}\implies r=\left\lfloor\frac{\frac{n}{k}}{\left\lfloor\frac{n}{kl}\right\rfloor}\right\rfloor$.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 50005;

int k;

int mu[N], pList[N], pID, muS[N];
bool notP[N];
void Euler(int n){
notP[0] = notP[1] = 1;
mu[1] = 1;
for(int i = 1; i <= n; i++){
if(notP[i] == 0) pList[++pID] = i, mu[i] = -1;
for(int j = 1; j <= pID; j++){
if(1ll * i * pList[j] > n) break;
notP[i * pList[j]] = 1;
if(i % pList[j] == 0){
mu[i * pList[j]] = 0;
break;
}
else mu[i * pList[j]] = -mu[i];
}
}
for(int i = 1; i <= n; i++) muS[i] = muS[i-1] + mu[i];
}

LL calc(int n, int m){
LL res = 0;
for(int l = 1, r; l <= min(n / k, m / k); l = r + 1){
r = min((n / k) / ((n / k) / l), (m / k) / ((m / k) / l));
res += 1ll * (muS[r] - muS[l-1]) * (n / k / l) * (m / k / l);
}
return res;
}

int main(){
Euler(50000);
int T; for(scanf("%d", &T); T; T--){
int a, b; scanf("%d%d%d", &a, &b, &k);
printf("%lld\n", calc(a, b));
}
return 0;
}

[HAOI2011]Problem b

题目链接

通过二维前缀和就可以把问题转换成上一道题。

不能乱开 long long,否则常数太大会 TLE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 50005;

int k;

int mu[N], pList[N], pID, muS[N];
bool notP[N];
void Euler(int n){
notP[0] = notP[1] = 1;
mu[1] = 1;
for(int i = 1; i <= n; i++){
if(notP[i] == 0) pList[++pID] = i, mu[i] = -1;
for(int j = 1; j <= pID; j++){
if(1ll * i * pList[j] > n) break;
notP[i * pList[j]] = 1;
if(i % pList[j] == 0){
mu[i * pList[j]] = 0;
break;
}
else mu[i * pList[j]] = -mu[i];
}
}
for(int i = 1; i <= n; i++) muS[i] = muS[i-1] + mu[i];
}

LL calc(int n, int m){
LL res = 0;
for(int l = 1, r; l <= min(n / k, m / k); l = r + 1){
r = min((n / k) / ((n / k) / l), (m / k) / ((m / k) / l));
res += 1ll * (muS[r] - muS[l-1]) * (n / k / l) * (m / k / l);
}
return res;
}

int main(){
Euler(50000);
int T; for(scanf("%d", &T); T; T--){
int a, b, c, d; scanf("%d%d%d%d%d", &a, &b, &c, &d, &k);
printf("%lld\n", calc(b, d) - calc(a-1, d) - calc(b, c-1) + calc(a-1, c-1));
}
return 0;
}

[SPOJ5971]LCMSUM

题目链接

令 $g(n)=\sum\limits_{d\mid n}d\cdot\varphi(d)$,如果我们可以预处理出 $g(n)$,那么我们就可以 $O(1)$ 回答了。我们这样预处理:枚举 $i$,将所有 $i\mid n$ 的 $g(n)$ 加上贡献 $i\cdot\varphi(i)$,这样预处理的复杂度是调和级数,其阶为 $O(n\lg n)$. 【这种预处理方式很重要!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 1000005;

int T, n;

int phi[N], pList[N], pID;
bool notP[N];
LL g[N];
void Euler(int n){
notP[0] = notP[1] = 1;
phi[1] = 1;
for(int i = 1; i <= n; i++){
if(notP[i] == 0) pList[++pID] = i, phi[i] = i - 1;
for(int j = 1; j <= pID; j++){
if(1ll * i * pList[j] > n) break;
notP[i * pList[j]] = 1;
if(i % pList[j] == 0){
phi[i * pList[j]] = phi[i] * pList[j];
break;
}
else phi[i * pList[j]] = phi[i] * (pList[j] - 1);
}
}
}

int main(){
Euler(1000000);
for(int i = 1; i <= 1000000; i++)
for(int j = 1; i * j <= 1000000; j++)
g[i*j] += 1ll * i * phi[i];
for(scanf("%d", &T); T; T--){
scanf("%d", &n);
printf("%lld\n", (g[n] + 1) * n / 2);
}
return 0;
}

[国家集训队]Crash的数字表格

题目链接

我们设

它是可以通过数论分块计算的,每一块是常数 $\frac{\left(\left\lfloor\frac{n}{l}\right\rfloor+1\right)\left\lfloor\frac{n}{l}\right\rfloor}{2}\cdot\frac{\left(\left\lfloor\frac{m}{l}\right\rfloor+1\right)\left\lfloor\frac{m}{l}\right\rfloor}{2}$ 乘上 $l^2\mu(l)+\cdots+r^2\mu(r)$,前缀和预处理即可。

那么原式可写作:

而它又是一个可以数论分块计算的,每一块是常数 $S\left(\left\lfloor\frac{n}{l}\right\rfloor,\left\lfloor\frac{m}{l}\right\rfloor\right)$ 乘上 $l+\cdots+r=\frac{(l+r)(r-l+1)}{2}$.

所以这道题是数论分块的嵌套,复杂度为 $O(\sqrt n\cdot\sqrt n)=O(n)$.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include<bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 10000005;
const LL MOD = 20101009;

int mu[N], pList[N], pID;
bool notP[N];
LL sum[N];
void Euler(int n){
notP[0] = notP[1] = 1;
mu[1] = 1;
for(int i = 1; i <= n; i++){
if(notP[i] == 0) pList[++pID] = i, mu[i] = -1;
for(int j = 1; j <= pID; j++){
if(1ll * i * pList[j] > n) break;
notP[i * pList[j]] = 1;
if(i % pList[j] == 0){
mu[i * pList[j]] = 0;
break;
}
else mu[i * pList[j]] = -mu[i];
}
}
for(int i = 1; i <= n; i++)
sum[i] = (sum[i-1] + 1ll * i * i % MOD * mu[i] % MOD) % MOD;
}

LL S(int n, int m){
LL res = 0;
if(n > m) swap(n, m);
for(int l = 1, r; l <= n; l = r + 1){
r = min(n / (n / l), m / (m / l));
res += 1ll * (1 + n / l) * (n / l) % MOD * (1 + m / l) % MOD * (m / l) % MOD * ((sum[r] - sum[l-1]) % MOD + MOD) % MOD * 15075757 % MOD;
res %= MOD;
}
return res;
}

int n, m;
LL ans;

int main(){
Euler(10000000);
scanf("%d%d", &n, &m);
if(n > m) swap(n, m);
for(int dl = 1, dr; dl <= n; dl = dr + 1){
dr = min(n / (n / dl), m / (m / dl));
ans += (1ll * (dl + dr) * (dr - dl + 1) % MOD * 10050505 % MOD) * S(n / dl, m / dl) % MOD;
ans %= MOD;
}
printf("%lld\n", ans);
return 0;
}

其实这道题还能继续优化:先鸽一下(雾


[luoguP2398]GCD SUM

题目链接

解法一:和上一道题差不多。

数论分块嵌套。

复杂度:$O(n)$

解法二

数论分块。

复杂度:$O(\sqrt n)$ (不算线性筛)

注:其实解法一继续推导可得解法二的表达式,不过推导的下一步不是很显然(要卷回去):

这个博客讲的也是这个,并给这种方法起了个名字叫做欧拉反演,哈哈。

解法一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<bits/stdc++.h>

using namespace std;

const int N = 100005;

int mu[N], pList[N], pID;
bool notP[N];
void Euler(int n){
notP[0] = notP[1] = 1;
mu[1] = 1;
for(int i = 1; i <= n; i++){
if(notP[i] == 0){
pList[++pID] = i;
mu[i] = -1;
}
for(int j = 1; j <= pID; j++){
if(1ll * i * pList[j] > n) break;
notP[i * pList[j]] = 1;
if(i % pList[j] == 0){
mu[i * pList[j]] = 0;
break;
}
else mu[i * pList[j]] = -mu[i];
}
}
}

int n, muS[N];

int main(){
Euler(100000);
for(int i = 1; i <= 100000; i++) muS[i] = muS[i-1] + mu[i];
scanf("%d", &n);
long long ans = 0;
for(int dl = 1, dr; dl <= n; dl = dr + 1){
dr = n / (n / dl);
long long res = 0;
for(int l = 1, r; l <= n / dl; l = r + 1){
r = (n / dl) / (n / dl / l);
res += 1ll * (muS[r] - muS[l-1]) * (n / dl / l) * (n / dl / l);
}
ans += 1ll * (dl + dr) * (dr - dl + 1) / 2 * res;
}
printf("%lld\n", ans);
return 0;
}

解法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<bits/stdc++.h>

using namespace std;

const int N = 100005;

int phi[N], pList[N], pID;
bool notP[N];
void Euler(int n){
notP[0] = notP[1] = 1;
phi[1] = 1;
for(int i = 1; i <= n; i++){
if(notP[i] == 0){
pList[++pID] = i;
phi[i] = i - 1;
}
for(int j = 1; j <= pID; j++){
if(1ll * i * pList[j] > n) break;
notP[i * pList[j]] = 1;
if(i % pList[j] == 0){
phi[i * pList[j]] = phi[i] * pList[j];
break;
}
else phi[i * pList[j]] = phi[i] * (pList[j] - 1);
}
}
}

int n;
long long phiS[N];

int main(){
Euler(100000);
for(int i = 1; i <= 100000; i++) phiS[i] = phiS[i-1] + phi[i];
scanf("%d", &n);
long long ans = 0;
for(int l = 1, r; l <= n; l = r + 1){
r = n / (n / l);
ans += 1ll * (phiS[r] - phiS[l-1]) * (n / l) * (n / l);
}
printf("%lld\n", ans);
return 0;
}

[NOI2010]能量采集

题目链接

题目要求的就是

和上一题一样了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<bits/stdc++.h>

using namespace std;

const int N = 100005;

int phi[N], pList[N], pID;
bool notP[N];
void Euler(int n){
notP[0] = notP[1] = 1;
phi[1] = 1;
for(int i = 1; i <= n; i++){
if(notP[i] == 0){
pList[++pID] = i;
phi[i] = i - 1;
}
for(int j = 1; j <= pID; j++){
if(1ll * i * pList[j] > n) break;
notP[i * pList[j]] = 1;
if(i % pList[j] == 0){
phi[i * pList[j]] = phi[i] * pList[j];
break;
}
else phi[i * pList[j]] = phi[i] * (pList[j] - 1);
}
}
}

int n, m;
long long phiS[N];

int main(){
Euler(100000);
for(int i = 1; i <= 100000; i++) phiS[i] = phiS[i-1] + phi[i];
scanf("%d%d", &n, &m); if(n > m) swap(n, m);
long long ans = 0;
for(int l = 1, r; l <= n; l = r + 1){
r = min(n / (n / l), m / (m / l));
ans += 1ll * (phiS[r] - phiS[l-1]) * (n / l) * (m / l);
}
printf("%lld\n", 2 * ans - 1ll * n * m);
return 0;
}

[SDOI2015]约数个数和

题目链接

引理:

证明:约数个数公式:$d(n)=d\left(\prod\limits_{i=1}^k{p_i}^{r_i}\right)=\prod\limits_{i=1}^k(r_i+1)$。考虑 $ij$ 的一个质因子 $p$,设 $i=i’\cdot{p}^{r_i}$ ,$j=j’\cdot{p}^{r_j}$,则 $p$ 对答案有 $r_i+r_j+1$ 的贡献。又考虑右边的式子,由于 $\gcd(x,y)=1$ 的约束,$p$ 只能从 $i$ 或 $j$ 其中一边取出,或者不取出,贡献仍是 $r_i+r_j+1$。证毕。

于是:

$S(1…n)$ 可以通过数论分块 $O(n\sqrt n)$ 地预处理出来,随后上式可通过数论分块 $O(\sqrt n)$ 地回答。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include<bits/stdc++.h>

using namespace std;

const int N = 50005;

int mu[N], pList[N], pID;
bool notP[N];
void Euler(int n){
notP[0] = notP[1] = 1;
mu[1] = 1;
for(int i = 1; i <= n; i++){
if(notP[i] == 0){
pList[++pID] = i;
mu[i] = -1;
}
for(int j = 1; j <= pID; j++){
if(1ll * i * pList[j] > n) break;
notP[i * pList[j]] = 1;
if(i % pList[j] == 0){
mu[i * pList[j]] = 0;
break;
}
else mu[i * pList[j]] = -mu[i];
}
}
}

int T, n, m, muS[N];
long long S[N];

int main(){
Euler(50000);
for(int i = 1; i <= 50000; i++) muS[i] = muS[i-1] + mu[i];
for(int i = 1; i <= 50000; i++){
for(int l = 1, r; l <= i; l = r + 1){
r = i / (i / l);
S[i] += (r - l + 1) * (i / l);
}
}
for(scanf("%d", &T); T; T--){
scanf("%d%d", &n, &m);
if(n > m) swap(n, m);
long long ans = 0;
for(int dl = 1, dr; dl <= n; dl = dr + 1){
dr = min(n / (n / dl), m / (m / dl));
ans += 1ll * (muS[dr] - muS[dl-1]) * S[n / dl] * S[m / dl];
}
printf("%lld\n", ans);
}
return 0;
}

[luoguP2257]YY的GCD

题目链接

最后一步的代换非常重要!详细逻辑如下:

然后令 $g(T)=\sum\limits_{p\mid T}{\mu\left(\frac{T}{p}\right)}\quad(p\in\text{prime})$,这是可以预处理的,即枚举质数 $p$,将 $p\mid n$ 的 $g(n)$ 加上贡献 $\mu\left(\frac{n}{p}\right)$,复杂度是 $O(n\cdot H(n))\sim O(n\lg n)$

随后数论分块,每一块是常数 $\left\lfloor\frac{n}{l}\right\rfloor\left\lfloor\frac{m}{l}\right\rfloor$ 乘上 $g(l)+\cdots+g(r)$,前缀和预处理即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<bits/stdc++.h>

using namespace std;

const int N = 10000005;

int mu[N], pList[N], pID;
bool notP[N];
void Euler(int n){
notP[0] = notP[1] = 1;
mu[1] = 1;
for(int i = 1; i <= n; i++){
if(notP[i] == 0){
pList[++pID] = i;
mu[i] = -1;
}
for(int j = 1; j <= pID; j++){
if(1ll * i * pList[j] > n) break;
notP[i * pList[j]] = 1;
if(i % pList[j] == 0){
mu[i * pList[j]] = 0;
break;
}
else mu[i * pList[j]] = -mu[i];
}
}
}

long long g[N];

int main(){
Euler(10000000);
for(int i = 1; i <= pID; i++)
for(int j = pList[i]; j <= 10000000; j += pList[i])
g[j] += mu[j / pList[i]];
for(int i = 1; i <= 10000000; i++) g[i] += g[i-1];
int T; for(scanf("%d", &T); T; T--){
int n, m; scanf("%d%d", &n, &m); if(n > m) swap(n, m);
long long ans = 0;
for(int l = 1, r; l <= n; l = r + 1){
r = min(n / (n / l), m / (m / l));
ans += 1ll * (g[r] - g[l-1]) * (n / l) * (m / l);
}
printf("%lld\n", ans);
}
return 0;
}

[luoguP2586]GCD

题目链接

求的东西和上一道题基本一样,但是没有多组数据,时限加紧。

注意上一道题为了 $O(\sqrt n)$ 地回答而进行了 $O(n\lg n)$ 的预处理,这个预处理在这道题里会超时。但是这道题只需要 $O(n)$ 回答就行了。

重新推一波式子:

预处理 $\varphi(n)$ 的前缀和,就可以 $O(n)$ 解决了。

上面的推导中最妙的一点是用上了 $\varphi(n)=\sum\limits_{i=1}^n[\gcd(i,n)=1]$ 这个定义式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<bits/stdc++.h>

using namespace std;

const int N = 10000005;

int phi[N], pList[N], pID;
bool notP[N];
void Euler(int n){
notP[0] = notP[1] = 1;
phi[1] = 1;
for(int i = 1; i <= n; i++){
if(notP[i] == 0){
pList[++pID] = i;
phi[i] = i - 1;
}
for(int j = 1; j <= pID; j++){
if(1ll * i * pList[j] > n) break;
notP[i * pList[j]] = 1;
if(i % pList[j] == 0){
phi[i * pList[j]] = phi[i] * pList[j];
break;
}
else phi[i * pList[j]] = phi[i] * (pList[j] - 1);
}
}
}

long long phiS[N];

int main(){
Euler(10000000);
for(int i = 1; i <= 10000000; i++) phiS[i] = phiS[i-1] + phi[i];
int n; scanf("%d", &n);
long long ans = 0;
for(int i = 1; pList[i] <= n; i++)
ans += 2 * phiS[n / pList[i]] - 1;
printf("%lld\n", ans);
return 0;
}
作者

xyfJASON

发布于

2020-07-26

更新于

2021-02-25

许可协议

评论