数论专题(莫反)题单&题解

博主耗时23天完成13道洛谷算法题题解,涵盖P2568 GCD、P2398 GCD SUM等题目。涉及最大公约数、能量采集、完全平方数等多种问题,解题过程运用欧拉反演、莫比乌斯反演、数论分块、杜教筛等算法知识。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

终于写完13题的题解了
用时 23 23 23

以此帖纪念洛谷红名

题单:

题单 \Huge \color{red}{题单} 题单

T1 P2568 GCD

题目描述

给定正整数 n n n,求 1 ≤ x , y ≤ n 1\le x,y\le n 1x,yn gcd ⁡ ( x , y ) \gcd(x,y) gcd(x,y) 为素数的数对 ( x , y ) (x,y) (x,y) 有多少对。

题目简化:求 ∑ p ∈ p r i m e s ∑ i = 1 n ∑ j = 1 n [ gcd ⁡ ( i , j ) = = p ] \sum_{p \in primes} \sum_{i=1}^{n} \sum_{j=1}^{n} [\gcd(i, j) == p] pprimesi=1nj=1n[gcd(i,j)==p]

转化为 ∑ p ∈ p r i m e s ∑ i = 1 ⌊ n p ⌋ ∑ j = 1 ⌊ n p ⌋ [ gcd ⁡ ( i , j ) = = 1 ] \sum_{p \in primes} \sum_{i=1}^{\lfloor \frac{n}{p} \rfloor} \sum_{j=1}^{\lfloor \frac{n}{p} \rfloor} [\gcd(i, j) == 1] pprimesi=1pnj=1pn[gcd(i,j)==1]

a n s = ∑ i = 1 ⌊ n p ⌋ ϕ ( i ) ans = \sum_{i=1}^{\lfloor \frac{n}{p} \rfloor} \phi(i) ans=i=1pnϕ(i)

#include <bits/stdc++.h>
using namespace std;
const long long N = 1e7 + 10;
long long euler[N], primes[N], sum[N], cnt;
bool st[N];
long long n;

void get_eulers(long long n)  // 线性筛法求1~n的欧拉函数
{
    euler[1] = 1;
    for (long long i = 2; i <= n; i ++ ) {
        if (!st[i]) {
            primes[ ++cnt ] = i;
            euler[i] = i - 1;
        }
        for (long long j = 1; primes[j] <= n / i; j ++ ) {
            long long t = primes[j] * i;
            st[t] = true;
            if (i % primes[j] == 0) {
                euler[t] = euler[i] * primes[j];
                break;
            }
            euler[t] = euler[i] * (primes[j] - 1);
        }
    }
}

int main() {
    scanf("%d", &n);
    get_eulers(n);
    for (long long i = 1; i <= n; i++) sum[i] = sum[i - 1] + euler[i];
    long long ans = 0;
    for (long long i = 1; i <= cnt && primes[i] <= n; i++) ans += 1ll * (sum[n / primes[i]] * 2) - 1;
    cout << ans;
    return 0;
}

T2 P2398 GCD SUM

题目描述

∑ i = 1 n ∑ j = 1 n gcd ⁡ ( i , j ) \sum_{i=1}^n \sum_{j=1}^n \gcd(i, j) i=1nj=1ngcd(i,j)

gcd ⁡ \gcd gcd 入手。

f k f_k fk gcd ⁡ ( i , j ) = = k \gcd(i,j) == k gcd(i,j)==k 的个数。

a n s = ∑ i = 1 n f i × i ans = \sum_{i=1}^{n} f_i \times i ans=i=1nfi×i

但这样不好算,因此引入 g g g 数组。

g k g_k gk k ∣ g c d ( i , j ) k | gcd(i,j) kgcd(i,j) 的个数。

g g g 的定义, g k = ⌊ n k ⌋ 2 g_k = \lfloor \frac{n}{k} \rfloor ^ 2 gk=kn2

这两个 g k g_k gk 相等,因此可以这么表示 f k f_k fk

f k = ⌊ n k ⌋ 2 − f 2 k − f 3 k − . . . . . . f_k = \lfloor \frac{n}{k} \rfloor ^ 2 - f_{2k} - f{3k} - ...... fk=kn2f2kf3k......

f f f 数组方便多了,可以逆序推导 f f f,最后统计答案。

#include <bits/stdc++.h>
using namespace std;
const long long N = 1e5 + 10;
long long n, f[N];
long long ans = 0;
int main() {
    scanf("%d", &n);
    for (long long i = n; i > 0; i--) {
        long long x = i; f[i] = (n / i) * (n / i);
        while (x + i <= n) {
            x += i;
            f[i] -= f[x];
        }
        ans += f[i] * i;
    }
    cout << ans;
    return 0;
}

T3 P1447 [NOI2010] 能量采集

题目描述

栋栋有一块长方形的地,他在地上种了一种能量植物,这种植物可以采集太阳光的能量。在这些植物采集能量后,栋栋再使用一个能量汇集机器把这些植物采集到的能量汇集到一起。

栋栋的植物种得非常整齐,一共有 n n n 列,每列有 m m m 棵,植物的横竖间距都一样,因此对于每一棵植物,栋栋可以用一个坐标 ( x , y ) (x, y) (x,y) 来表示,其中 x x x 的范围是 1 1 1 n n n y y y 的范围是 1 1 1 m m m,表示是在第 x x x 列的第 y y y 棵。

由于能量汇集机器较大,不便移动,栋栋将它放在了一个角上,坐标正好是 ( 0 , 0 ) (0, 0) (0,0)

能量汇集机器在汇集的过程中有一定的能量损失。如果一棵植物与能量汇集机器连接而成的线段上有 k k k 棵植物,则能量的损失为 2 k + 1 2k + 1 2k+1。例如,当能量汇集机器收集坐标为 ( 2 , 4 ) (2, 4) (2,4) 的植物时,由于连接线段上存在一棵植物 ( 1 , 2 ) (1, 2) (1,2),会产生 3 3 3 的能量损失。注意,如果一棵植物与能量汇集机器连接的线段上没有植物,则能量损失为 1 1 1。现在要计算总的能量损失。

下面给出了一个能量采集的例子,其中 n = 5 n = 5 n=5 m = 4 m = 4 m=4,一共有 20 20 20 棵植物,在每棵植物上标明了能量汇集机器收集它的能量时产生的能量损失。

在这个例子中,总共产生了 36 36 36 的能量损失。


题意转化:求

( 2 × ∑ i = 1 n ∑ j = 1 m gcd ⁡ ( i , j ) ) − n × m \bigg( 2 \times \sum_{i=1}^{n} \sum_{j=1}^{m} \gcd(i,j) \bigg) - n \times m (2×i=1nj=1mgcd(i,j))n×m

引理:欧拉反演

n = ∑ d ∣ n ϕ ( d ) n = \sum_{d|n} \phi(d) n=dnϕ(d)

现在重点只考虑 ∑ i = 1 n ∑ j = 1 m gcd ⁡ ( i , j ) \sum_{i=1}^{n} \sum_{j=1}^{m} \gcd(i,j) i=1nj=1mgcd(i,j)

转化为: ∑ i = 1 n ∑ j = 1 m ∑ d ∣ g c d ( i , j ) ϕ ( d ) \sum_{i=1}^{n} \sum_{j=1}^{m} \sum_{d|gcd(i,j)} \phi(d) i=1nj=1mdgcd(i,j)ϕ(d)

= ∑ d = 1 n ϕ ( d ) ∑ i = 1 n ∑ j = 1 m [ d ∣ g c d ( i , j ) ] =\sum_{d=1}^{n} \phi(d) \sum_{i=1}^{n} \sum_{j=1}^{m} [d|gcd(i,j)] =d=1nϕ(d)i=1nj=1m[dgcd(i,j)]

= ∑ d = 1 n ϕ ( d ) ⌊ n d ⌋ ⌊ m d ⌋ =\sum_{d=1}^{n} \phi(d) \lfloor \frac{n}{d} \rfloor \lfloor \frac{m}{d} \rfloor =d=1nϕ(d)dndm

直接线性筛 ϕ \phi ϕ 就行了。

#include <bits/stdc++.h>
using namespace std;
const long long N = 1e7 + 10;
long long euler[N], primes[N], sum[N], cnt;
bool st[N];
long long n, m;

void get_eulers(long long n)  // 线性筛法求1~n的欧拉函数
{
    euler[1] = 1;
    for (long long i = 2; i <= n; i ++ ) {
        if (!st[i]) {
            primes[ ++cnt ] = i;
            euler[i] = i - 1;
        }
        for (long long j = 1; primes[j] <= n / i; j ++ ) {
            long long t = primes[j] * i;
            st[t] = true;
            if (i % primes[j] == 0) {
                euler[t] = euler[i] * primes[j];
                break;
            }
            euler[t] = euler[i] * (primes[j] - 1);
        }
    }
}

int main() {
    scanf("%d%d", &n, &m);
    get_eulers(n);
    for (long long i = 1; i <= n; i++) sum[i] = sum[i - 1] + euler[i];
    long long ans = 0;
    for (int d = 1; d <= n; d++) {
        ans += euler[d] * (n / d) * (m / d);
    }
    cout << 2 * ans - n * m;
    return 0;
}

T4 P2522 [HAOI2011]Problem b

题目描述

对于给出的 n n n 个询问,每次求有多少个数对 ( x , y ) (x,y) (x,y),满足 a ≤ x ≤ b a \le x \le b axb c ≤ y ≤ d c \le y \le d cyd,且 gcd ⁡ ( x , y ) = k \gcd(x,y) = k gcd(x,y)=k gcd ⁡ ( x , y ) \gcd(x,y) gcd(x,y) 函数为 x x x y y y 的最大公约数。

简化题意:求

∑ i = a b ∑ j = c d [ gcd ⁡ ( i , j ) = = k ] \sum_{i=a}^{b} \sum_{j=c}^{d} [\gcd(i,j) == k] i=abj=cd[gcd(i,j)==k]

这题有一个类似二维前缀和的思想,或者说是容斥。

A n s = s o l v e ( ( 1 , b ) , ( 1 , d ) ) − s o l v e ( ( 1 , b ) , ( 1 , c − 1 ) ) − s o l v e ( ( 1 , a − 1 ) , ( 1 , d ) ) + s o l v e ( ( 1 , a − 1 ) , ( 1 , c − 1 ) ) Ans = solve((1,b),(1,d))-solve((1,b),(1,c-1))-solve((1,a-1),(1,d))+solve((1,a-1),(1,c-1)) Ans=solve((1,b),(1,d))solve((1,b),(1,c1))solve((1,a1),(1,d))+solve((1,a1),(1,c1))

至此,完成了 1 − n , 1 − m 1-n,1-m 1n,1m的转化。只要求解这个子问题就行了。

P3455 [POI2007]ZAP-Queries

这题即是 T4 的子问题。

题目描述

密码学家正在尝试破解一种叫 BSA 的密码。
他发现,在破解一条消息的同时,他还需要回答这样一种问题:
给出 a , b , d a,b,d a,b,d,求满足 1 ≤ x ≤ a 1 \leq x \leq a 1xa 1 ≤ y ≤ b 1 \leq y \leq b 1yb,且 gcd ⁡ ( x , y ) = d \gcd(x,y)=d gcd(x,y)=d 的二元组 ( x , y ) (x,y) (x,y) 的数量。
因为要解决的问题实在太多了,他便过来寻求你的帮助。

f ( k ) = ∑ i = 1 a ∑ i = 1 b [ gcd ⁡ ( i , j ) = = k ] f(k) = \sum_{i=1}^{a} \sum_{i=1}^{b} [\gcd(i,j)==k] f(k)=i=1ai=1b[gcd(i,j)==k]

g ( k ) = ∑ n ∣ k f ( k ) = ⌊ a n ⌋ ⌊ b n ⌋ g(k) = \sum_{n|k} f(k) = \lfloor \frac{a}{n} \rfloor \lfloor \frac{b}{n} \rfloor g(k)=nkf(k)=nanb

根据莫反有: f ( k ) = ∑ n ∣ k μ ( ⌊ k n ⌋ ) F ( k ) f(k) = \sum_{n|k} \mu(\lfloor \frac{k}{n} \rfloor) F(k) f(k)=nkμ(⌊nk⌋)F(k)

根据题目, A n s = f ( d ) Ans = f(d) Ans=f(d)

A n s Ans Ans 用莫反的式子表示出来:

A n s = ∑ d ∣ k μ ( ⌊ k d ⌋ ) F ( k ) Ans = \sum_{d|k} \mu(\lfloor \frac{k}{d} \rfloor) F(k) Ans=dkμ(⌊dk⌋)F(k)

A n s = ∑ d ∣ k μ ( ⌊ k d ⌋ ) ⌊ a k ⌋ ⌊ b k ⌋ Ans = \sum_{d|k} \mu(\lfloor \frac{k}{d} \rfloor) \lfloor \frac{a}{k} \rfloor \lfloor \frac{b}{k} \rfloor Ans=dkμ(⌊dk⌋)kakb

t = b k t=\frac{b}{k} t=kb,改为枚举 t t t,则:

A n s = ∑ t = 1 min ⁡ ( ⌊ a d ⌋ , ⌊ b d ⌋ ) μ ( t ) ⌊ a t d ⌋ ⌊ b t d ⌋ Ans = \sum_{t=1}^{\min(\lfloor \frac{a}{d} \rfloor , \lfloor \frac{b}{d} \rfloor)} \mu(t) \lfloor \frac{a}{td} \rfloor \lfloor \frac{b}{td} \rfloor Ans=t=1min(⌊da,db⌋)μ(t)tdatdb

至此,该子问题得到了解决,已经是 O ( N ) O(N) O(N) 的效率了。

但是由于多组数据,需要采用数论分块优化到 O ( N ) O(\sqrt N) O(N )

子问题代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int mu[N], prime[N], tot = 0, sum[N];
bool st[N];

int read(){
    int x = 0; int zf = 1; char ch = ' ';
    while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
    if (ch == '-') zf = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); return x * zf;
}

inline void init(int n) {
	mu[1] = 1;
    for (register int i = 2; i <= n; i++) {
        if (!st[i]) prime[++tot] = i, mu[i] = -1;
        for (register int j = 1; j <= tot && prime[j] * i <= n; j++) {
            st[prime[j] * i] = 1;
            if (i % prime[j] != 0) mu[i * prime[j]] = -mu[i];
            else { mu[prime[j] * i] = 0; break; }
        }
    }
    for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i];
}

int k;

int f(int x, int y) {
    int res = 0;
    for (int l = 1, r; l <= min(x, y); l = r + 1) {
        r = min(x / (x / l), y / (y / l));
        res += (sum[r] - sum[l - 1]) * ((x / k) / l) * ((y / k) / l);
    }
    return res;
}

int main() {
	init(50000);
	int T; T = read();
	while (T--) {
		static int a, b; a = read(), b = read(), k = read(); 
		printf("%lld\n", f(a, b));
	}
	return 0;
}

原问题只需要像开头说的一样容斥解决。

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
int mu[N], prime[N], tot = 0, sum[N];
bool st[N];

int read(){
    int x = 0; int zf = 1; char ch = ' ';
    while (ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
    if (ch == '-') zf = -1, ch = getchar();
    while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); return x * zf;
}

inline void init(int n) {
	mu[1] = 1;
    for (register int i = 2; i <= n; i++) {
        if (!st[i]) prime[++tot] = i, mu[i] = -1;
        for (register int j = 1; j <= tot && prime[j] * i <= n; j++) {
            st[prime[j] * i] = 1;
            if (i % prime[j] != 0) mu[i * prime[j]] = -mu[i];
            else { mu[prime[j] * i] = 0; break; }
        }
    }
    for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + mu[i];
}

int k;

int f(int x, int y) {
    int res = 0;
    for (int l = 1, r; l <= min(x, y); l = r + 1) {
        r = min(x / (x / l), y / (y / l));
        res += (sum[r] - sum[l - 1]) * ((x / k) / l) * ((y / k) / l);
    }
    return res;
}

int main() {
	init(50000);
	int T; T = read();
	while (T--) {
		static int a, b, c, d; a = read(), b = read(), c = read(), d = read(), k = read(); 
		printf("%lld\n", f(b, d) - f(b, c - 1) - f(a - 1, d) + f(a - 1, c - 1));
	}
	return 0;
}

T5 P4318 完全平方数

题目描述

小 X 自幼就很喜欢数。但奇怪的是,他十分讨厌完全平方数。他觉得这些数看起来很令人难受。由此,他也讨厌所有是完全平方数的正整数倍的数。然而这丝毫不影响他对其他数的热爱。

这天是小X的生日,小 W 想送一个数给他作为生日礼物。当然他不能送一个小X讨厌的数。他列出了所有小X不讨厌的数,然后选取了第 K个数送给了小X。小X很开心地收下了。

然而现在小 W 却记不起送给小X的是哪个数了。你能帮他一下吗?

题意简化:

多组数据,每次求第 K K K不含大于 1 1 1 的完全平方因子的正整数。

由于当时正好学杜教筛,因此就用了杜教筛的方法。

详见 OI-Wiki

杜教筛可以在低于线性时间内处理数论函数的前缀和。
个人认为就是个记忆化+预处理优化。

回到原题,平方因子和莫比乌斯函数有点关系。

题目要求第 K K K 个满足条件的数,因此有: ∑ i = 1 a n s [ μ ( i ) ≠ 0 ] = = K \sum_{i=1}^{ans} [\mu(i) \neq 0] == K i=1ans[μ(i)=0]==K,并且 μ ( a n s ) ≠ 0 \mu(ans) \neq 0 μ(ans)</

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值