能力综合提升-数学-1
0x01 素数
线性筛素数
首先是线性筛吧!这个必须要背的。
注意:在线性筛中,每一个合数都是被最小的质因子筛掉。
时间复杂度 $O(n)$。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vector<int> prime;
bitset<Max> notprime;
void sift(const int Max)
{
F(i, 2, Max)
{
if(!notprime[i]) prime.push_back(i);
for(int j : prime)
{
if(i * j > Max) break;
notprime[i * j] = true;
if(i % j == 0) break;
}
}
}
Miller-Rabin
判断一个数是不是素数。
- 暴力试除法 $O(n),O(\sqrt n)$,不可接受。
- Fermat 小定理:$p$ 为素数时,$a^p\equiv a\pmod p$ —— 然而逆命题并不一定成立,这些素数被称为 Carmichael 数($561=3\times 11\times 17$)。在 $10^9$ 以内,这样的数有 646 个,因此不能满足需求。
- Miller-Rabin 素性测试。
以下内容来自 warzone 的 Blog。
Miller-Rabin 素性测试是对 Fermat 小定理的优化。具体的,我们有二次探测定理:
若 $p$ 为奇素数,则 $x^2\equiv 1\pmod p$ 的解为 $x\equiv \pm 1\pmod p$
PPT 上是这么说的:如果 $n$ 为素数,取 $a<n$,设 $n-1=d\times 2^r$,则要么 $a^d\equiv 1 \pmod{n}$,要么 $\exists 0\leq i<r,\operatorname{s.t.} a^{d\times 2^i} \equiv -1\pmod{n}$。其证明见 Wikipedia。
具体的,若 $p$ 是奇数,则 $p-1$ 是偶数:
- 选取一个底数 $a$,初始化指数 $d=p-1$,判断 $a^d \bmod p$ 是否为 $1$,若否则非素数。
- 重复 $d\gets \dfrac{d}{2}$ 并判断,直到 $d$ 为奇数。
- 最后,若 $a^d \not\equiv \pm 1\pmod p$,则 $p$ 非素数,否则 $p$ 大概率是一个素数。
1
2
3
4
5
6
7
8
9
10
11
12
bool check(const int a, const ll p)
{
ll d = p - 1, res = fastpow(a, d, p);
if(res != 1) return false;
while(!(d & 1))
{
res = fastpow(a, d >>= 1, p);
if(res == p - 1) return true;
else if(res != 1) return false;
}
return true;
}
初学者写 Miller-Rabin 时可能会犯的错误:
- 没有特判 $2^{p-1} \equiv -1\pmod p$ 的情况。
- 先计算 $a^d\bmod p$,再检查 $d$ 的奇偶性。
若随机选取 $k$ 个底数,Miller-Rabin 的错误率为 $\dfrac{1}{4^k}$。也就是说,选取 $20 \sim 40$ 个底数,正确率是可以接受的。
对于竞赛的特殊环境,有
- 判断
unsigned int
范围内,选取2, 7, 61
三个底数即可。 - 判断
unsigned long long
范围内,选取2, 325, 9375, 28178, 450775, 9780504, 1795265022
七个底数即可。 - 判断
unsigned long long
范围内,选取前十二个素数(到37
为止)作为底数即可,OEIS。
注:不是哥们,你这个写的太他妈丑陋了,还是换一个抄吧。
注:
fastpow
要注意别炸long long
,用__int128
。
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
inline ll fastpow(ll a, ll b, const ll mod)
{
ll ans = 1;
while (b)
{
if (b & 1)
ans = (__int128)ans * a % mod;
a = (__int128)a * a % mod;
b >>= 1;
}
return ans;
}
bool check(const ll a, const ll n)
{
ll d = n - 1;
while(d)
{
ll res = fastpow(a, d, n);
if(res != 1 && res != n - 1)
return false;
if((d & 1) || res == n - 1)
return true;
d >>= 1;
}
return true;
}
vector<int> MRTest = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37};
bool Miller_Rabin(const ll p)
{
if(p > 40)
{
for(int i : MRTest)
if(!check(i, p)) return false;
return true;
}
else
{
for(int i : MRTest)
if(i == p) return true;
return false;
}
}
Pollard-Rho
质因数分解。
暴力枚举依然是 $O(\sqrt n)$ 的。
利用生日悖论减少枚举量。
生日悖论:从 $n$ 个数中随机选取 $k$ 个两两之差全部不等于 $c$ 的概率约为 $O(\dfrac{1}{n^{k^2}})$。
所以:找到 $k$ 个数,检查这 $k$ 个数两两的差与 $n$ 的 $\gcd$。
伪随机函数 $f_{i}=f^2_{i-1}+c$
它具有的优秀的性质:如果 $\gcd(f_{i}-f_{j}, n)>1$,则 $\gcd(f_{i+1}-f_{j+1}, n)=\gcd((f_{i}-f_{j})(f_{i}+f_{j}),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
mt19937_64 rand64(time(nullptr));
ll rnd(const ll l, const ll r) // 左闭右开
{
return l + (rand64() % (r - l));
}
ll f(const ll x, const ll c, const ll p)
{
return (x * x + c) % p;
}
ll Pollard_Rho(const ll n)
{
ll x = rnd(0, n), y = x;
// 关于这里 y 和下面 k 的含义见下面正文
ll c = rnd(3, n), d = 1;
// 当 c = 0 或 c = 2 的情况应当避免
// 来自算法导论,书上也没有具体解释原因
for(int i = 1, k = 2; d == 1; i ++)
{
x = f(x, c, n);
d = gcd(qabs(y - x), n);
if(i == k)
{
y = x;
k <<= 1;
}
}
return d;
}
但是有一个问题:这个伪随机数的函数会包含死循环————循环节的产生不难理解:在模 $n$ 意义下,整个函数的值域只能是 $0,1,2,\cdots,n-1$。总有一次,函数在迭代的过程中会带入之前的值,得到相同的结果。生成数列的轨迹很像一个希腊字母 $\rho$ ,所以整个算法的名字叫做 Pollard-Rho。
这个问题可以基于 Floyd 算法优化。具体的,我们希望判断环的存在,通俗来讲就是“龟兔赛跑”。假设环的长度为 $c$,在环内有 $f_i=f_{i+kc}$。乌龟每次迭代一次,兔子每次迭代两次,如果二者相同,则必有 $f_{2i}=f_{i+kc}$,这样就找到了环。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ll Pollard_Rho(const ll n)
{
if(n == 4) // 初始跳两步没法分解 4,需要特判
return 2;
ll x = rnd(0, n), y = x;
ll c = rnd(3, n), d = 1;
x = f(x, c, n);
y = f(f(y, c, n), c, n);
while(x != y)
{
x = f(x, c, n);
y = f(f(y, c, n), c, n);
d = gcd(abs(x - y), n);
if(d > 1)
return d;
}
return n;
}
考虑做 $\gcd$ 的时间复杂度是 $O(\log n)$ 的,如果多次使用时间复杂度会大到爆炸。其实,每次计算 $\gcd$ 是不必要的,可以计算一段连续的乘积,然后再做 $\gcd$ 也是一样的效果(倍增的思想)。
- 枚举 $i=2^j,2^j+1,\cdots,2^{j+1}-1$,计算 $\gcd(\prod(f_i-f_{2^j}),p)$。
- 但是这样长度太长才检查一次,所以加一个每 $t$ 次乘积之后就检查一次的条件。一般 $t=128$(此处无任何道理说是)。
注:做乘法要防止炸
long long
,必须使用__int128
。
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
ll mul(ll a, ll b, ll p)
{
return __int128(a) * b % p;
}
mt19937_64 rand64(time(nullptr));
ll rnd(const ll l, const ll r)
{
return l + (rand64() % (r - l));
}
ll f(const ll x, const ll c, const ll p)
{
return (mul(x, x, p) + c) % p;
}
int cnt;
ll Pollard_Rho(ll n)
{
if(n == 4)
return 2;
ll x = rnd(0, n), y = x;
ll c = rnd(3, n), d = 1;
x = f(x, c, n);
y = f(f(y, c, n), c, n);
for(int lim = 1; x != y; lim = qmin(128, lim << 1))
{
ll cnt = 1;
F(i, 1, lim)
{
ll tmp = mul(cnt, qabs(x - y), n);
if(tmp == 0)
break;
cnt = tmp;
x = f(x, c, n);
y = f(f(y, c, n), c, n);
}
d = gcd(cnt, n);
if(d > 1)
return d;
}
return n;
}
0x02 最大公约数
辗转相除法
首先是最简单的欧几里得算法!
1
2
3
4
ll gcd(ll a, ll b)
{
return b == 0 ? a : gcd(b, a % b);
}
不过时间复杂度是 $O(\log V)$。
当我们需要访问 gcd()
很多次时,就需要用到其他的算法了。
一种基于值域预处理的快速 gcd
以下内容来自 moongazer 的 Blog。
这是一种 $O(V)$ 时间预处理 $O(1)$ 询问求 gcd
的算法。
记:$V$ 为值域,$\mathbb{P}$ 为素数集,集合 ${a_1, a_2, \cdots, a_n}$ 是 $x$ 的分解 $\Longleftrightarrow x=\prod\limits_{i=1}^n a_i$。
定理一:可以把值域内的每个 $x$ 分解成 ${a, b, c}$ 且必有 $a, b, c$ 要么 $\leq \sqrt x$ 要么 $\in \mathbb{P}$(称为合法分解)。
反证。不失一般性,令 $a\leq b\leq c$。若 $c\notin \mathbb{P} \wedge c > \sqrt x$,则必存在 $c$ 的分解 ${d, e}$ 且有 $d\leq e$ 和 $d\leq \sqrt x$。
而 $a\times b=\frac{x}{c}<\sqrt x$,于是也有 $x$ 的分解 ${d, a\times b, e}$。若 $e>\sqrt x$ 则重复上述过程直到满足条件。
定理二:对于 gcd(x, y)
,考虑 $x$ 的合法分解 ${a, b, c}$,分别考虑它们对答案的贡献。$a$ 的贡献为 $v_a=\gcd(a, y)$,$b$ 的贡献为 $v_b=\gcd(b, \frac{y}{v_a})$ (因为 $v_a$ 这个因子已经被计算过,不能重复计算),$c$ 的贡献同理,$v_c=\gcd(c, \frac{y}{v_av_b})$。
我们发现实现的难点在于如何在 $O(V)$ 时间内进行分解,询问部分较为容易。
记最小质因子函数 $f(x)$ 为 $x$ 的最小质因子。
如何分解?对于 $x\geq 2$,找到 $x$ 的最小质因子 $p=f(x)$ 和 $\frac{x}{p}$ 的一个合法分解 ${a_0, b_0, c_0}$,则 $x$ 的一个合法分解为 $\operatorname{sort}{a_0\times p, b_0, c_0}$。
试着证明它:
- $x\in \mathbb{P}$ 时显然成立,分解为 ${1, 1, x}$;
- $p\leq \sqrt[4]{x}$ 时有 $a_0\leq \sqrt[3]{\frac{x}{p}}$ 则 $a_0\times p \leq x^{\frac{1}{3}}p^{\frac{2}{3}}\leq x^{\frac{1}{3}}x^{\frac{1}{6}}=\sqrt x$;
- $p > \sqrt[4]{x}$ 时
- $a_0 = 1$,有 $a_0 \times p = p \leq \sqrt x$;
- $a_0 \neq 1$,则 $x\notin \mathbb{P}$,$q=f(\frac{x}{p})\geq f(x)=p$,则 $p\leq q\leq a_0\leq b_0\leq c_0$,从而 $p\times a_0\times b_0\times c_0 > (\sqrt[4] x)^4 = x$ 与 $p\times a_0\times b_0\times c_0=x$ 矛盾,故此情况不存在。
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
constexpr int maxm = 1e6 + 10;
constexpr int maxk = 1e3 + 10;
struct Fac
{
int a, b, c;
Fac() : a(1), b(1), c(1) {}
int operator[](int idx) // 方便使用 for 来调用每个元素
{
switch(idx)
{
case 1: return a;
case 2: return b;
case 3: return c;
default: break;
}
return 0;
}
void rec(Fac t, int p) // 赋值并让 a *= p,并排序
{
a = t.a * p, b = t.b, c = t.c;
sort();
}
void sort() // 注意到 bc 一定是顺序,只需要看 a 的位置即可
{
if(a > b) qswap(a, b);
if(b > c) qswap(b, c);
}
} fac[maxm]; //用于存放分解
bitset<maxm> isPrime; // bitset 存是否是质数
int Pri[maxm], cnt; // 质数集 P
int pre[maxk][maxk]; // 预处理的 sqrt(V) 内的 gcd
const int V = 1e6, T = 1e3; // T = sqrt(V)
void Pre() // 线性筛预处理
{
isPrime.flip(); // 全部标为质数
F(i, 2, V) // 线性筛
{
if(isPrime[i])
{
fac[i].c = i;
Pri[++ cnt] = i;
}
for(int j = 1, k = Pri[j] * i; k <= V; j ++, k = Pri[j] * i)
{
isPrime[k] = false;
fac[k].rec(fac[i], Pri[j]); // 把质数的分解偷过来,并且 Pre[j] 一定是最小质因子 p
if(i % Pri[j] == 0) // 最小的保证是因为 i % Pri[j] == 0 时就 break 掉了
break;
}
}
// 预处理 gcd 数组
// 因为由辗转相除法,a > b 时有 gcd(a, b) = gcd(b, a % b),这可以预处理实现
F(i, 0, sqrt(V))
pre[0][i] = pre[i][0] = i;
F(i, 1, sqrt(V))
F(j, 1, i)
pre[i][j] = pre[j][i] = pre[j][i % j];
}
int gcd(int a, int b)
{
int ans = 1;
F(i, 1, 3)
{
int tmp;
if(fac[a][i] > sqrt(V)) // 查看 a 的分解的第 i 个和 sqrt(V) 的关系
{
if(b % fac[a][i])
tmp = 1; // 互质,没有贡献
else
tmp = fac[a][i]; // 整除,贡献就是本身
}
else
tmp = pre[fac[a][i]][b % fac[a][i]]; // 否则两边都是 sqrt(V) 内的内容可以预处理解决
b /= tmp; // 防止算重
ans *= tmp; // 计算贡献
}
return ans;
}
例题
求出 $\forall k = 1,2,\cdots, n$,在给定的 ${a_i}$ 中挑出 $k$ 个数,最大的 $\gcd$。
$n \leq 10^4,a_i\leq 10^6$。
一个相当经典的结论是,直接去求 $\gcd$ 是没有必要的,枚举因子反而是更简单也更正确的。
对于每个 $a_i$,$O(\sqrt n)$ 暴力枚举因子把它们存到一个桶里,并且显然有 $ans_k\geq ans_{k+1}$。
那你从上往下扫一遍就好啦。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main()
{
int n = readint();
F(i, 1, n)
{
read(a);
F(j, 1, sqrt(a))
if(a % j == 0)
b[j] ++, b[a / j] += (a != j * j);
}
for(int i = 1, j = 1e6; i <= n; i ++)
{
while(b[j] < i)
j --;
write('\n', j);
}
return 0;
}
例题
$n$ 次询问,求出所有可能的 $x$ 的个数,使 $\gcd(x, a_0)=a_1 \land \operatorname{lcm}(x, b_0)=b_1$。
$n\leq 2000, 1\leq a_0,a_1,b_0,b_1\leq 2\times 10^9$。
你看到这个东西很自然的就要想到
\[\begin{cases}\gcd(x, a_0)=a_1\Longrightarrow (x \bmod a_1=a_0\bmod a_1=0) \land (\gcd(\frac{x}{a_1},\frac{a_0}{a_1})=1)\\\operatorname{lcm}(x, b_0)=b_1\Longrightarrow (b_1 \bmod x=b_1\bmod b_0=0) \land (\gcd(\frac{b_1}{x},\frac{b_1}{b_0})=1)\end{cases}\]那你直接 $O(\sqrt{V})$ 枚举 $x$ 然后依次检查这四个条件即可。总时间复杂度 $O(n\sqrt{V})$ 在 $10^9$ 级别但是跑的飞快。
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
bool check(int c2, int d2, int a1, int b1, int x)
{
int d1 = b1 / x;
if(x % a1)
return false;
int c1 = x / a1;
if(gcd(c1, c2) > 1)
return false;
if(gcd(d1, d2) > 1)
return false;
return true;
}
int workwork()
{
int a0, a1, b0, b1;
read(a0, a1, b0, b1);
if(a0 % a1)
return 0;
int c2 = a0 / a1;
if(b1 % b0)
return 0;
int d2 = b1 / b0;
int ans = 0;
F(i, 1, sqrt(b1))
{
if(b1 % i)
continue;
ans += check(c2, d2, a1, b1, i);
if(i * i < b1)
ans += check(c2, d2, a1, b1, b1 / i);
}
return ans;
}
0x03 欧拉函数
$\varphi(n)=\sum\limits_{i=1}^{n}[\gcd(n, i) =1]$。
性质
$n\in \mathbb P\Longrightarrow \varphi(n)=n-1$
是积性函数。即 $\forall a, b, \gcd(a, b)=1\Longrightarrow\varphi(ab)=\varphi(a)\varphi(b)$
$n$ 为奇数时 $\varphi(2n)=\varphi(n)$
$n = \sum\limits_{d\mid n}\varphi(d)$
$n = p^k, p\in \mathbb{P}, \varphi(n)=p^k-p^{k-1}$
$\varphi(n)=n\prod\limits_{i=1}^{s}\dfrac{p_i-1}{p_i}$,$p_i$ 是质因数
$\varphi(\gcd(m, n))\varphi(mn)=\gcd(m, n)\varphi(m)\varphi(n)$
$n>1\Longrightarrow 2\mid \varphi(n)$
部分证明见 OI-Wiki。
单点求 $\varphi(n)$
主要利用性质 $\varphi(n)=n\prod\limits_{i=1}^{s}\dfrac{p_i-1}{p_i}$($p_i$ 是质因数)进行 $O(\sqrt n)$ 的质因数分解。
可以用 Pollard-Rho 优化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int Phi(int n)
{
int ans = n;
for(int i = 2; i * i <= n; i ++)
if(n % i == 0) // 对于每个质因数
{
ans = ans / i * (i - 1);
while(n % i == 0) // 除掉所有的当前质因数
n /= i;
}
if(n > 1) // 分解最后剩下单个质数
ans = ans / n * (n - 1);
return ans;
}
线性筛 $\varphi(n)$
只需要在线性筛素数的基础上改进即可。
还记得线性筛的原理吗?每一个合数都是被最小的质因子筛掉。
记 $p_1$ 为 $n$ 的最小质因子,有 $n = p_1 \times n^{\prime}$。
分两个情况讨论:
- $n^{\prime} \bmod p_1=0$,那么 $n^{\prime}$ 包含了 $n$ 的所有质因子。
\(\begin{aligned} \varphi(n) &= n\times\prod\dfrac{p_i-1}{p_i} \\ &= p_1\times n^{\prime}\times \prod\dfrac{p_i-1}{p_i} \\ &= p_1\times \varphi(n^{\prime}) \\ \end{aligned}\)
- $n^{\prime} \bmod p_1\neq 0$,那么 $\gcd(p_1, n^{\prime})=1$,由欧拉函数的积性有
\(\begin{aligned} \varphi(n) &= \varphi(p_1\times n^{\prime}) \\ &= \varphi(p_1)\times\varphi(n^{\prime})\\ &= (p_1-1)\times \varphi(n^{\prime}) \\ \end{aligned}\)
其中 $n^{\prime}<n$ 即 $\varphi(n^{\prime})$ 已经被算出。
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
vector<int> prime;
bitset<Max> notprime;
int Phi[Max];
void sift(const int Max)
{
Phi[1] = 1; // 特判
F(i, 2, Max)
{
if(!notprime[i])
{
prime.push_back(i);
Phi[i] = i - 1; // 质数的情况
}
for(int j : prime)
{
if(i * j > Max) break;
notprime[i * j] = true;
if(i % j == 0)
{
Phi[i * j] = Phi[i] * j; // 情况 1,直接乘 p_1
break;
}
Phi[i * j] = Phi[i] * Phi[j]; // 情况 2,乘 Phi(p_1) 也就是 (p_1 - 1)
}
}
}
例题
$n\times n$ 的方阵,求视线能看到的人的个数。
忽略几个特殊的点,我们注意到,可以被看到的人必须满足 $\gcd(x,y)=1$。 证明是显然的,若 $d=\gcd(x,y)\neq 1$,则 $(\dfrac{x}{d},\dfrac{y}{d})$ 必然能把 $(x,y)$ 挡住,所以我们只需要求出范围内互质的对数即可。
由于把 $(1,1)$ 看成坐标原点,因此 $x,y\leq n-1$ 而非 $n$。
注意到 $i,j$ 的对称性,只需要计算 $i>j$ 的情况。剩下的 $(0,1),(1,0),(1,1)$ 最后补上即可。
\(\begin{aligned} ans &= 2+\sum\limits_{i=1}^{n-1}\sum\limits_{j=1}^{n-1}[\gcd(i, j)=1] \\ &= 2+\sum\limits_{i=1}^{n-1}[\gcd(i, i)=1]+2\times\sum\limits_{i=1}^{n-1}\sum\limits_{j=1}^{i-1}[\gcd(i, j)=1]\\ &= 3 + 2\times\sum\limits_{i=1}^{n-1}\varphi(i)\\ \end{aligned}\)
求 $\varphi(n)$ 前 $n-1$ 项和即可。时间复杂度 $O(n)$。
例题
求 $\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[\gcd(i, j)\in \mathbb{P}]$。
\(\begin{aligned} ans &= \sum\limits_{p\in \mathbb{P}}\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}[\gcd(i, j)=p] \\ &= \sum\limits_{p\in \mathbb{P}}\sum\limits_{i=1}^{\lfloor \frac{n}{p}\rfloor}\sum\limits_{j=1}^{\lfloor \frac{n}{p}\rfloor}[\gcd(i, j)=1] \\ &= \sum\limits_{p\in \mathbb{P}}(-1+2\times \sum\limits_{i=1}^{\lfloor \frac{n}{p}\rfloor}\sum\limits_{j=1}^{i}[\gcd(i, j)=1]) \\ &= \sum\limits_{p\in \mathbb{P}}(-1+2\times \sum\limits_{i=1}^{\lfloor \frac{n}{p}\rfloor}\varphi(i)) \\ \end{aligned}\)
$O(n)$ 预处理 $\varphi(n)$ 及 $\sum\limits_{i=1}^{n}\varphi(i)$,枚举每个质数即可。时间复杂度 $O(n)$。
例题
求 $\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\gcd(i, j)$。
\(\begin{aligned} ans &= \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{k=1}^{n} [\gcd(i, j)=k]\times k \\ &= \sum\limits_{k=1}^{n} k\times\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n} [\gcd(i, j)=k] \\ \end{aligned}\)
对于 $\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n} [\gcd(i, j)=k]$ 的处理,套路同上。时间复杂度 $O(n)$。