---
tags: VNOJ,
title: Thách Thức Lập Trình Xuân Giáp Thìn - Thử thách Vũ Môn
author: theTai123
---
<style>
.markdown-body { max-width: 2048px; }
</style>
$\Huge \text{🐲 Thách Thức Lập Trình Xuân Giáp Thìn - Thử thách Vũ Môn
}$
-----
###### 🔗 Link: [https://oj.vnoi.info/problem/dovui_2024_a](https://oj.vnoi.info/problem/dovui_2024_a)
###### 📌 Tags: `Math`,`Brute-force`,`Sieve`
###### 👤 Writer: @theTai123
###### 📋 Content:
[TOC]
_____
## Tóm Tắt
Cho một dãy số gồm $n$ số nguyên dương $a_1,a_2,...,a_n$. Với mỗi số nguyên dương $a_i$ đếm xem có bao nhiêu nghiệm nguyên dương thỏa mãn phương trình $x+y+gcd(x,y)=a_i$. $(1\leq n \leq2\cdot10^5,1\leq a_i \leq4\cdot10^6)$.
*Từ đây, gọi $m$ là $\max (a_i$)*.
-----
## Subtask 1
> **Tags:** `Brute-force`
> [color=lightgreen]
Ở subtask này, ta vét cạn tất cả các cặp $(x,y)$ nguyên dương và kiểm tra có thỏa mãn không.
> **Complexity:** $O(n \cdot m^2)$
> [color=lightgreen]
:::success
:::spoiler Code của theTai123
```cpp=
#include <iostream>
#include <algorithm>
#define pb push_back
#define pop pop_back
#define fi first
#define se second
typedef long long ll;
typedef double db;
template<typename A, typename B> bool minimize(A& a, const B& b) {return a > b ? a = b, 1 : 0;}
template<typename A, typename B> bool maximize(A& a, const B& b) {return a < b ? a = b, 1 : 0;}
using namespace std;
int n;
int res;
void process(int x) {
res = 0;
for(int i = 1; i < x; i++) {
for(int j = 1; j < x; j++) {
if(i + j + __gcd(j, i) == x) res++;
}
}
cout << res << " ";
}
int main() {
ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n;
for(int i = 1; i <= n; i++) {
int x; cin >> x;
process(x);
}
return 0;
}
```
:::
------
## Subtask 2
>**Tags:** `Math` `Brute-force`
> [color=lightgreen]
Từ phương trình $x+y+gcd(x,y)=a_i$ ,ta đặt $gcd(x,y)=q$. Vì $x$ và $y$ chia hết cho $q$ nên ta có $x=aq, y=bq$ $(a,b\in\mathbb N^*)$.
Suy ra:
$aq+bq+q=a_i$
$\Leftrightarrow q \cdot (a+b+1)=a_i$
$\Leftrightarrow a+b+1={a_i \over q}$
$\Leftrightarrow a+b={a_i \over q}-1$.
Để $a+b \in \mathbb N^*$ thì ${a_i \over q} -1\in \mathbb N^*$ suy ra $q \in Ư(a_i)$ và $q \neq a_i$.
Nếu $gcd(aq,bq)=q$ thì $gcd(a,b)=1$ vậy $a$ và $b$ là hai số nguyên tố cùng nhau.
Do đó nhiệm vụ của ta là đếm tất cả các cặp $a,b \in \mathbb N^*$ sao cho $a+b={a_i \over q}-1$ với $q \in Ư(a_i), q \neq a_i$ và $gcd(a,b)=1$.
> **Complexity:** $O(n \cdot m \log m)$
> [color=lightgreen]
:::success
:::spoiler Code của theTai123
```cpp=
#include <iostream>
#include <algorithm>
#define pb push_back
#define pop pop_back
#define fi first
#define se second
typedef long long ll;
typedef double db;
template<typename A, typename B> bool minimize(A& a, const B& b) {return a > b ? a = b, 1 : 0;}
template<typename A, typename B> bool maximize(A& a, const B& b) {return a < b ? a = b, 1 : 0;}
using namespace std;
int n;
int get(int t) {
int res = 0;
for(int j = 1; j < t; j++) {
if(__gcd(j, t - j) == 1) {
res++;
}
}
return res;
}
void process(int x) {
int res = 0;
for(int i = 1; i < x; i++) {
if(x % i == 0) res += get(x / i - 1);
}
cout << res << " ";
}
int main() {
ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n;
for(int i = 1; i <= n; i++) {
int x; cin >> x;
process(x);
}
return 0;
}
```
:::
------
## Subtask 3
> **Tags:** `Math`
> [color=lightgreen]
Từ subtask 2, gọi ${a_i\over q}-1$ là $u$. Ta thấy có đúng $u-1$ cặp số $a,b$ thõa mãn $a+b=u$.
Với mỗi cặp $a,b$ bất kì, nếu $gcd(a,u)=1$ thì $gcd(a,u-a)=1$ hay $gcd(a,b)=1$. Vậy nên ta chỉ cần đếm tất cả các số nguyên tố cùng nhau với $u$ trong đoạn $[1;u-1]$ và ta có thể dễ dàng đếm được bằng cách sử dụng phi hàm Euler $ϕ$ với độ phức tạp $O(\sqrt m)$.
> **Complexity:** $O(n \cdot m \log \sqrt m)$
> [color=lightgreen]
:::success
:::spoiler Code của theTai123
```cpp=
#include <iostream>
#include <math.h>
#define pb push_back
#define pop pop_back
#define fi first
#define se second
typedef long long ll;
typedef double db;
template<typename A, typename B> bool minimize(A& a, const B& b) {return a > b ? a = b, 1 : 0;}
template<typename A, typename B> bool maximize(A& a, const B& b) {return a < b ? a = b, 1 : 0;}
using namespace std;
int n;
int phi(int k) {
if(k == 0) return 0;
int res = k - 1;
for (int i = 2; i * i <= k; ++i) {
if (k % i == 0) {
while(k % i == 0) {
k /= i;
}
res -= res / i;
}
}
if (k > 1) {
res -= res / k;
}
return res;
}
void process(int x) {
int res = 0;
for(int i = 1; i < x; ++i) {
if(x % i == 0) res += phi(x / i - 1);
}
cout << res << " ";
}
int main() {
ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n;
for(int i = 1; i <= n; i++) {
int x; cin >> x;
process(x);
}
return 0;
}
```
:::
------
## Subtask 4
> **Tags:** `Math` `Sieve`
> [color=lightgreen]
Tương tự như subtask 3 nhưng thay vào đó ta sẽ sử dùng sàng thay vì phải tính lại kết quả.
Độ phức tạp khi sàng là $O(m \log \log m)$.
Để tăng tốc, thay vì kiểm tra ước từ 1 tới $m$, ta sẽ kiểm tra từ 1 tới $\sqrt m$ và kiểm tra xem $m\over i$ có phải ước của $m$ không.
> **Complexity:** $O(n \cdot \sqrt m + m \log \log m)$
> [color=lightgreen]
:::success
:::spoiler Code của theTai123
```cpp=
#include <iostream>
#include <math.h>
#define pb push_back
#define pop pop_back
#define fi first
#define se second
#define MAXA 4000000
typedef long long ll;
typedef double db;
template<typename A, typename B> bool minimize(A& a, const B& b) {return a > b ? a = b, 1 : 0;}
template<typename A, typename B> bool maximize(A& a, const B& b) {return a < b ? a = b, 1 : 0;}
using namespace std;
int n, res;
int phi[MAXA + 7];
void sieve() {
for(int i = 1; i <= MAXA + 5; i++) {
phi[i] = i - 1;
}
for(int i = 2; i <= MAXA + 5; i++) {
if(phi[i] == i - 1) {
for(int j = i; j <= MAXA + 5; j += i) {
phi[j] -= phi[j] / i;
}
}
}
}
void process(int x) {
res = 0;
for(int i = 1; i * i < x; i++) {
if(x % i == 0) {
int u = x / i;
res += phi[u - 1];
if(x % u == 0) res += phi[x / u - 1];
}
}
int u = sqrt(x);
if(u * u == x) res += phi[u - 1];
cout << res << " ";
}
int main() {
ios_base::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n;
int x;
sieve();
for(int i = 1; i <= n; i++) {
cin >> x;
process(x);
}
return 0;
}
```
:::