Bài toán

Nguồn: Codewars

Đọc hiểu

Trong lý thuyết số, hàm Carmichael của một số nguyên dương $n$, ký hiệu $\lambda(n)$, là số nguyên dương m nhỏ nhất sao cho $a^m \equiv 1 \mod n$ với mọi $a \leq n$ là các số nguyên tố cùng nhau với n.

Ví dụ $n = 8$. Các số nguyên tố cùng nhau với $8$ không vượt quá $8: 1, 3, 5, 7.$ $$ \begin{aligned} 1^2 \equiv 1 \mod 8 \\ 3^2 \equiv 1 \mod 8 \\ 5^2 \equiv 1 \mod 8 \\ 7^2 \equiv 1 \mod 8 \\ \end{aligned} $$

Vì vậy, $\lambda(8) = 2$.

Có 2 cách để tính hàm này:

  • Với mỗi số nguyên tố cùng nhau với n không vượt quá n, kiểm tra $a^m \equiv 1 \mod n$ với $m$ tăng dần. Cách này không hiệu quả đối với số lớn.
  • Dùng công thức chính xác như hàm phi Euler. Đây là công thức tường minh để tính $\lambda(n)$ dựa trên phân tích thừa số nguyên tố của n.

Ở phương pháp thứ hai, công thức được suy ra từ công thức bcnn của $\lambda$ và hàm phi:

  • Cho $p_1,…,p_n$ là các thừa số nguyên tố của n với các số mũ $w_1,…,w_n$ của chúng: $$ \begin{aligned} \lambda(n) & = \lambda(p_1^{w_1} \times p_2^{w_2} \times … \times p_n^{w_n}) \\ \lambda(n) & = lcm(\lambda(p_1^{w_1}), \lambda(p_2^{w_2}),…, \lambda(p_n^{w_n})) \\ \end{aligned} $$ Đối với mỗi số nguyên tố p, ta có sự liên kết giữa $\lambda(p^w)$ và hàm phi Euler $\phi(p^w)$: $$ \begin{aligned} \lambda(p^w) &= \phi(p^w) & p &> 2\\ \lambda(p^w) &= \phi(p^w) & p &= 2, w < 3\\ \lambda(p^w) &= \frac{1}{2} \phi(p^w) & p &= 2, w \geq 3 \end{aligned} $$

Viết hàm Carmichael, nhận vào một số nguyên $n \geq 1$ và trả về $\lambda(n)$. Nếu $n < 1$ trả về 0

Khoảng nhập: $1 \leq n \leq 1e10$

Lời giải

Hàm phi Euler của $n = p_1^{w_1} \times p_2^{w_2} \times…\times p_k^{w_n}$ được tính bởi công thức sau: $$ \phi(n) = n \times (1 - \frac{1}{p_1}) \times (1 - \frac{1}{p_2}) \times…\times (1 - \frac{1}{p_k})$$

Cho $n = p^w$ công thức được viết lại như sau: $$ \phi(n) = p^w \times (1 - \frac{1}{p}) = p^{w-1} \times (p-1)$$

Kết hợp vừa phân tích thừa số nguyên tố của n, vừa tính $\lambda$ của chúng thông qua $\phi$ mình có đoạn chương trình sau:

#include <vector>
#include <math.h>

long long Carmichael(long long n){
	if (n < 1) return 0;
	if (n == 1) return 1;
	std::vector<long long> factors;
	for(long long i = 2; i*i <= n; i += 2){
		long long w = 0;
		while(n % i == 0){
			w++;
			n /= i;
		}
		if (i == 2 && w >= 3)
			factors.push_back((pow(i, w-1) * (i-1))/2);
		else if(i >= 2 && w > 0)
			factors.push_back(pow(i, w-1) * (i-1));
		if(i == 2) i--;
	}
	if(n != 1) factors.push_back(n-1);
}

Lúc này, mình đã có được một dãy các kết quả của $\lambda(p_1^{w_1}), \lambda(p_2^{w_2}),…, \lambda(p_n^{w_n})$. Công đoạn cuối là tính bội chung nhỏ nhất của chúng. Một trong những phương pháp đơn giản là lấy tích chia cho ước chung lớn nhất: $$ LCM(a, b) = \frac{a \times b}{GCD(a,b)} = a \times \frac{b}{GCD(a, b)} $$

Với a là kết quả cần tính và b là các phần tử $\lambda(p_i^{w_i})$ trong factors, mình có thể khởi động vòng lặp tính lcm như sau:

long long res = 1;
for(auto i : factors)
	res *= i/gcd(res, i);
return res;

Hàm gcd sử dụng giải thuật Euclid

long long gcd(int a, int b) {
	while(b){
		long long t = a % b;
		a = b;
		b = t;
	}
	return a;
}

Cảm ơn bạn vì đã đọc.