Trước hết, ta cần tìm thặng dư "không chính phương" để thực hiện hai thuật toán bên dưới.
Vì một nửa số phần tử trong tập là thặng dư không chính phương, nên ta sẽ duyệt từng số từ cho đến khi gặp được số thỏa mãn. Để kiểm tra một số thỏa mãn hay không, ta sử dụng cách Tiêu chuẩn Euler bên trên.
Để thuật toán hiệu quả hơn, bạn nên sinh số ngẫu nhiên và kiểm tra đến khi tìm được. Xác suất lần thử tìm được là , nên xác suất sau lần thử mà bạn chưa tìm ra là .
Bài viết xin không đề cập phần chứng minh thuật toán. Bạn đọc tham khảo tại Wikipedia.
Bước 1: ta phân tích với lẻ
Bước 2: Chọn là một thặng dư không chính phương bất kỳ.
Bước 3: Gán
Bước 4: Lặp
Tìm nhỏ nhất sao cho
Nếu thì chính là đáp án cần tìm.
Nếu thì đặt gán:
Code C++ minh họa:
int pow_mod(long long a, long long k, long long M) {
long long ans = 1;
for (; k > 0; a = a * a % M, k >>= 1) {
if (k & 1) {
ans = ans * a % M;
}
}
return ans;
}
int Tonelli_Shanks(int a, int p) {
if (p == 2) {
return (a & 1);
}
int S = 0, Q = p - 1;
while (Q % 2 == 0) {
S++;
Q /= 2;
}
int z = 2;
while (legendre_symbol(z, p) != p - 1) {
z++;
}
int x = pow_mod(a, (Q + 1) >> 1, p), b = pow_mod(a, Q, p);
int m, v, e, u;
while (b % p != 1) {
m = 0, v = 1; // v = 2^m
while (pow_mod(b, v, p) != 1) {
m++;
v <<= 1;
}
e = Q << (S - m - 1);
u = pow_mod(z, e, p);
x = (1LL * x * u) % p;
b = (((1LL * u * u) % p) * b) % p;
}
return x;
}
Bạn đọc có thể thấy nó khá giống số phức, chỉ thay bằng mà thôi.
Mục đích của chúng ta là tính theo . Như các bạn nghĩ đến, chúng ta sẽ sử dụng phép lũy thừa nhanh và có chút thay đổi cho phù hợp bài toán:
- Ký hiệu:
Phần tử đơn vị:
Xét phép nhân số
Phép lũy thừa:
Các phép toán trên chỉ là một số tính chất của trường hữu hạn . Để có kiến thức đầy đủ hơn, bạn đọc tham khảo trên Wikipedia.
Bài viết xin không đề cập phần chứng minh thuật toán. Bạn đọc tham khảo tại Wikipedia.
Bước 1: Tìm sao cho là thặng dư không chính phương modulo
Bước 2: Ta tính .
Khi đó, tìm được chính là nghiệm của bài toán.
Nói cách khác là trên
Code C++ minh họa
Về cài đặt, như đã nói ở trên, khá giống số phức nên việc cài đặt cũng tương tự như vậy.
int a, p;
int k; // thặng dư không chính phương mod p
struct Complex {
int re, im;
Complex(int a = 0, int b = 0) {
re = a;
im = b;
}
Complex operator*(const Complex &o) {
Complex res;
res.re = (1LL * re * o.re + (1LL * im * k % p) * o.im) % p;
res.im = (1LL * re * o.im + 1LL * im * o.re) % p;
return res;
}
Complex pow(long long k) {
Complex res = Complex(1, 0), A = *this;
while (k) {
if (k & 1) res = res * A;
A = A * A;
k >>= 1;
}
return res;
}
};
int Cipolla(long long a, long long p) {
if (p == 2) {
return (a & 1);
}
// Tìm k = b^2 - a, sao cho k không chính phương
int b = 2;
while (true) {
b %= p;
k = (b * b - a) % p;
if (k < 0) k += p;
if (legendre_symbol(k, p) == p - 1) break;
b++;
}
// Ta cần tìm <b, 1>^((p+1)/2)
return Complex(b, 1).pow((p + 1) >> 1).re;
}