Hướng dẫn giải của Số Nasty
Nộp một lời giải chính thức trước khi tự giải là một hành động có thể bị ban.
Lời giải này đang bị ẩn cho đến khi bạn chọn mở ra.
Chúng tôi khuyên bạn nên tự thử giải bài trước. Việc mở lời giải có thể làm lộ mất ý tưởng chính trước khi bạn có cơ hội tự giải.
Bạn phải đăng nhập để mở lời giải này.
Đăng nhậpTác giả: , , ,
Hiểu bài toán
Một số nguyên dương x được gọi là 'số Nasty' nếu tồn tại hai cặp ước (a, b) và (c, d) sao cho a * b = x, c * d = x và một trong hai điều kiện sau thỏa mãn: |a - b| = c + d hoặc |c - d| = a + b. Nói cách khác, hiệu của một cặp ước phải bằng tổng của một cặp ước khác (hoặc ngược lại). Yêu cầu viết chương trình kiểm tra nhiều số, với mỗi số in ra 1 nếu nó là số Nasty, ngược lại in ra 0.
Các cách tiếp cận
Cách Quy hoạch động - Đánh dấu tổng và hiệu
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
void solve() {
int n;
cin >> n;
vector<pair<int, int>> factors;
// Thu thập các cặp ước (i, n/i)
for (int i = 1; i * i <= n; ++i) {
if (n % i == 0) {
factors.push_back({i, n / i});
}
}
// Dùng mảng đánh dấu cho tổng (tối đa ~2000 cho n nhỏ)
// Nếu n lớn, cần dùng unordered_set thay vì vector<bool>
int max_val = 2005;
vector<bool> marked_sum(max_val, false);
for (auto p : factors) {
int sum = p.first + p.second;
if (sum < max_val) marked_sum[sum] = true;
}
bool nasty = false;
for (auto p : factors) {
int diff = abs(p.first - p.second);
if (diff < max_val && marked_sum[diff]) {
nasty = true;
break;
}
}
cout << (nasty ? 1 : 0) << "\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
// freopen("bai3.inp", "r", stdin);
// freopen("bai3.out", "w", stdout);
int t = 1;
cin >> t;
while (t--) solve();
return 0;
}
- Time Complexity: O(sqrt(n))
- Space Complexity: O(1) (hoặc O(k) với k là số lượng ước)
Phương pháp này dựa trên việc quan sát rằng ta chỉ cần kiểm tra mối quan hệ giữa các cặp ước.
- Liệt kê tất cả các cặp ước (a, b) của n.
- Tính tổng S = a + b và hiệu D = |a - b| của mỗi cặp.
- Kiểm tra xem có cặp ước nào có hiệu D bằng với tổng S của một cặp ước khác hay không.
Trong code, ta dùng một mảng marked_sum để đánh dấu các giá trị tổng đã xuất hiện. Sau đó duyệt lại các cặp ước, nếu hiệu của cặp ước đó đã được đánh dấu là tổng của cặp ước khác, ta kết luận là số Nasty.
Lưu ý: Giải pháp này chỉ hiệu quả nếu giá trị tổng và hiệu nằm trong một giới hạn nhỏ (ví dụ < 2005 như trong code mẫu). Nếu n có thể rất lớn, cần dùng unordered_set để lưu trữ các tổng thay vì mảng đánh dấu.
Cách Brute Force (Duyệt tất cả cặp ước)
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
bool isNasty(long long x) {
vector<pair<long long, long long>> f;
// Thu thập tất cả các cặp ước
for (long long i = 1; i * i <= x; i++) {
if (x % i == 0) {
long long j = x / i;
f.push_back({j, i}); // Lưu theo thứ tự giảm dần (j >= i)
}
}
int k = f.size();
// Duyệt tất cả các cặp khác nhau
for (int a = 0; a < k; a++) {
for (int b = a + 1; b < k; b++) {
long long L1 = f[a].first, R1 = f[a].second;
long long L2 = f[b].first, R2 = f[b].second;
// Kiểm tra điều kiện số Nasty
// 1. Hiệu của cặp a bằng tổng của cặp b: (L1 - R1) == (L2 + R2)
// 2. Hiệu của cặp b bằng tổng của cặp a: (L2 - R2) == (L1 + R1)
if (L1 - R1 == L2 + R2 || L2 - R2 == L1 + R1)
return true;
}
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N;
cin >> N;
while (N--) {
long long x;
cin >> x;
cout << (isNasty(x) ? 1 : 0) << "\n";
}
return 0;
}
- Time Complexity: O(d^2) (d là số lượng ước của n)
- Space Complexity: O(d)
Đây là cách tiếp cận trực quan nhất.
- Tìm tất cả các cặp ước của x và lưu vào vector.
- Duyệt qua tất cả các cặp ước khác nhau (i, j).
- Với mỗi cặp (i, j) và một cặp khác (k, l), ta kiểm tra trực tiếp 2 điều kiện:
- |i - j| == k + l
- |k - l| == i + j
Phương pháp này đơn giản để hiểu và code, nhưng trở nên chậm nếu số lượng ước của x lớn (ví dụ x có nhiều ước nhất khi x là số có nhiều ước nhỏ, tốn O(d^2) thời gian). Tuy nhiên, với các bài toán thi đấu thông thường, số lượng ước không quá lớn nên phương pháp này thường chấp nhận được.
Cách Định lý số học (Công thức toán học)
#include <iostream>
#include <cmath>
using namespace std;
// Hàm kiểm tra số chính phương
bool isPerfectSquare(long long n) {
long long root = sqrt(n);
return root * root == n;
}
// Kiểm tra số Nasty dựa trên công thức (c - d) = (a + b)
// Ta có: a + b = sqrt((a+b)^2) = sqrt((a-b)^2 + 4ab) = sqrt((c-d)^2 + 4x)
// Nếu (c-d) = (a+b) => (a+b)^2 = (c-d)^2 => (c-d)^2 + 4x = (c-d)^2 => 4x = 0 (Không thể)
// Đổi lại: (a-b) = (c+d)
// => (a-b)^2 = (c+d)^2 = (c-d)^2 + 4cd = (c-d)^2 + 4x
// => (a-b)^2 - (c-d)^2 = 4x
// => [(a-b) - (c-d)] * [(a-b) + (c-d)] = 4x
// Để đơn giản, ta xét điều kiện cần là tồn tại cặp (a,b) sao cho (a-b) là tổng của một cặp ước khác.
// Phát biểu toán học: Số Nasty là số mà tại đó tồn tại cặp ước (a,b) sao cho (a-b) = sqrt(4x + d^2) là số nguyên.
// Hay dễ hơn: Kiểm tra xem có thể tạo thành "Hiệu" bằng "Tổng" không.
bool solve_math(long long x) {
// Duyệt qua tất cả các ước i của x
for (long long i = 1; i * i <= x; i++) {
if (x % i == 0) {
long long a = x / i;
long long b = i;
// Hiệu của cặp (a, b)
long long diff = a - b;
// Kiểm tra xem diff có thể là tổng của một cặp ước nào đó không?
// diff = c + d && c * d = x
// => c và d là nghiệm của phương trình: t^2 - diff * t + x = 0
// Delta = diff^2 - 4x
long long delta = diff * diff - 4 * x;
if (delta >= 0) {
long long sqrt_delta = sqrt(delta);
if (sqrt_delta * sqrt_delta == delta) {
return true;
}
}
}
}
return false;
}
int main() {
int N;
cin >> N;
while(N--) {
long long x;
cin >> x;
cout << (solve_math(x) ? 1 : 0) << "\n";
}
return 0;
}
- Time Complexity: O(sqrt(x))
- Space Complexity: O(1)
Đây là phương pháp tối ưu nhất, loại bỏ hoàn toàn việc so sánh các cặp ước với nhau (loại bỏ vòng lặp lồng nhau).
Bài toán có thể được chứng minh bằng đại số:
Ta cần tìm hai cặp (a, b) và (c, d) sao cho a * b = c * d = x và |a - b| = c + d.
Giả sử a - b = c + d. Khi đó a = b + c + d.
Thay vào ab = x: (b + c + d)b = x => b^2 + (c+d)b - x = 0.
Điều này có nghĩa là b là nghiệm của phương trình bậc 2: t^2 - (c+d)t + x = 0.
Tương tự, nếu (c-d) = a+b, ta có c-d là tổng của một cặp ước.
Tuy nhiên, một cách phát hiện số Nasty hiệu quả hơn là dựa vào tính chất của delta:
Nếu (a - b) = (c + d), ta có (a - b)^2 = (c + d)^2.
Vì c+d = sqrt((c-d)^2 + 4cd) = sqrt((c-d)^2 + 4x).
Nên (a - b)^2 = (c-d)^2 + 4x.
Đặt d = |c - d| (hiệu của cặp thứ hai). Ta có (a - b)^2 - d^2 = 4x.
=> (a - b - d) * (a - b + d) = 4x.
Cách tiếp cận trong code:
Duyệt qua tất cả các ước i của x. Gọi a = x/i, b = i. Tính hiệu diff = a - b.
Kiểm tra xem diff có phải là tổng của một cặp ước nào đó không.
diff = c + d và cd = x.
Phương trình t^2 - diff*t + x = 0 phải có nghiệm nguyên.
Delta = diff^2 - 4x. Kiểm tra Delta có phải là số chính phương hay không.
Nếu có, ta tìm được cặp (c,d) thỏa mãn.
Độ phức tạp là O(sqrt(x)) do chỉ cần duyệt các ước một lần.
Phân tích độ phức tạp
| Cách tiếp cận | Time | Space | Tên |
|---|---|---|---|
| 1 | O(sqrt(n)) | O(1) (hoặc O(k) với k là số lượng ước) | Quy hoạch động - Đánh dấu tổng và hiệu |
| 2 | O(d^2) (d là số lượng ước của n) | O(d) | Brute Force (Duyệt tất cả cặp ước) |
| 3 | O(sqrt(x)) | O(1) | Định lý số học (Công thức toán học) |
Bài học kinh nghiệm
- Bài toán yêu cầu mối quan hệ giữa 'Hiệu' của một cặp ước và 'Tổng' của một cặp ước khác.
- Một phát biểu quan trọng (thường dùng trong các bài toán tương tự): Số Nasty là số mà tại đó phương trình
t^2 - k*t + x = 0có nghiệm nguyên, vớiklà hiệu của một cặp ước nào đó củax. - Số Nasty là số có ít nhất 3 ước (không tính 1 và chính nó), tức là không phải số nguyên tố.
Lỗi thường gặp
- Sai lệch trong việc xử lý số mũ lớn: Sử dụng
long longđể tránh tràn số khi tính bình phương hoặc hiệu. - Lỗi logic so sánh các cặp ước: Cần đảm bảo so sánh đúng điều kiện
|a - b| = c + dthay vìa - b = c + d(cần xét cả trường hợp âm). - Giả định giới hạn giá trị: Solution 2 & 3 giả sử tổng và hiệu nhỏ (< 2005), điều này không đúng với mọi test case của bài toán tổng quát.
Bình luận