Hướng dẫn giải của DI CHUYỂN CHẬU HOA
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
Bài toán yêu cầu di chuyển N cây hoa từ tọa độ (Xi, Yi) ban đầu về một trong hai trục tọa độ Ox (tại (Xi, 0)) hoặc Oy (tại (0, Yi)). Mục tiêu là tìm cách di chuyển sao cho khoảng cách giữa hai cây hoa xa nhau nhất (tính theo bình phương) là nhỏ nhất có thể.
Quan sát: Sau khi di chuyển, tất cả các điểm sẽ nằm trên hai trục tọa độ. Do đó, khoảng cách giữa hai điểm bất kỳ sẽ là:
- Khoảng cách giữa hai điểm cùng trên trục Ox: $|xa - xb|^2$
- Khoảng cách giữa hai điểm cùng trên trục Oy: $|ya - yb|^2$
- Khoảng cách giữa một điểm trên Ox và một điểm trên Oy: $xa^2 + yb^2$ (vì điểm trên Ox là $(xa, 0)$ và điểm trên Oy là $(0, yb)$).
Bài toán trở thành một bài toán tối ưu hóa lựa chọn 2N biến nhị phân (mỗi cây hoa chọn 1 trong 2 vị trí) để minimise giá trị lớn nhất trong tập hợp các khoảng cách phát sinh.
Các cách tiếp cận
Cách Quy hoạch động dạng Knapsack (Tối ưu theo tổng bình phương)
#include <bits/stdc++.h>
using namespace std;
const long long INF = 1e18;
int N;
struct Pt { long long x, y; };
vector<Pt> a;
// dp[i][j] là tổng bình phương tọa độ của i điểm được chọn lên trục thứ j
// Trạng thái: tổng bình phương tọa độ Ox được chọn và Oy được chọn
map<pair<long long, long long>, long long> dp;
int main() {
cin >> N;
a.resize(N);
for(int i=0; i<N; i++) cin >> a[i].x >> a[i].y;
dp[{0, 0}] = 0;
for(int i=0; i<N; i++) {
map<pair<long long, long long>, long long> next_dp;
for(auto &state : dp) {
long long sum_x = state.first.first;
long long sum_y = state.first.second;
long long cost = state.second;
// Chọn đưa lên Ox
long long new_sum_x = sum_x + a[i].x * a[i].x;
long long new_cost_x = cost + a[i].x * a[i].x;
// ... (Logic này không hoàn toàn đúng trong bài toán Maximization)
// Lưu ý: Bài toán này về bản chất là Minimize Max(Các bình phương)
// DP thông thường tính tổng không giải quyết được trực tiếp bài toán Min-Max
}
}
// ... code mẫu bị thiếu ...
}
- Time Complexity: O(N * Sum^2)
- Space Complexity: O(N * Sum)
Cách tiếp cận này cố gắng sử dụng DP để theo dõi tổng bình phương của các tọa độ được chọn. Tuy nhiên, đây là một sai lầm phổ biến. Bài toán yêu cầu minimize giá trị LỚN NHẤT (Max) trong các khoảng cách, chứ không phải minimize tổng các bình phương. Do đó, việc sử dụng DP tích lũy tổng sẽ không tìm được nghiệm tối ưu cho bài toán Max-Min này. Ví dụ, chọn một tọa độ rất lớn có thể làm hỏng nghiệm dù tổng bình phương nhỏ.
Cách Thăm dò biên (Binary Search) + 2-SAT
#include <bits/stdc++.h>
using namespace std;
using int64 = long long;
struct Pt { int64 x, y; };
int N;
vector<Pt> a;
inline int node(int i, int val){ return (i<<1) | (val&1); }
inline int neg(int u){ return u ^ 1; }
void addImp(vector<vector<int>>& g, int u, int v){ g[u].push_back(v); }
// Hàm kiểm tra tính khả thi với biên M
bool feasible(int64 M){
vector<vector<int>> g(2*N);
for(int i=0; i<N; i++){
for(int j=i+1; j<N; j++){
// Tính bình phương khoảng cách giữa các cặp điểm có thể
// Dùng __int128 để tránh tràn số khi nhân
__int128 d_xx = (__int128)(a[i].x - a[j].x) * (a[i].x - a[j].x);
__int128 d_yy = (__int128)(a[i].y - a[j].y) * (a[i].y - a[j].y);
__int128 d_xy_i = (__int128)a[i].x * a[i].x + (__int128)a[j].y * a[j].y;
__int128 d_xy_j = (__int128)a[j].x * a[j].x + (__int128)a[i].y * a[i].y;
// Nếu hai điểm cùng lên Ox mà biên M bị vi phạm
if(d_xx <= M) {
// Constraint: (i, Ox) AND (j, Ox) -> Forbidden
// Biến đổi: !(A && B) <=> !A || !B
// Tương đương: A -> !B và B -> !A
addImp(g, node(i, 1), neg(node(j, 1))); // i=Ox => j!=Ox
addImp(g, node(j, 1), neg(node(i, 1))); // j=Ox => i!=Ox
}
// Nếu hai điểm cùng lên Oy
if(d_yy <= M) {
addImp(g, node(i, 0), neg(node(j, 0)));
addImp(g, node(j, 0), neg(node(i, 0)));
}
// Nếu điểm i lên Ox, điểm j lên Oy
if(d_xy_i <= M) {
addImp(g, node(i, 1), neg(node(j, 0)));
addImp(g, node(j, 0), neg(node(i, 1)));
}
// Nếu điểm j lên Ox, điểm i lên Oy
if(d_xy_j <= M) {
addImp(g, node(j, 1), neg(node(i, 0)));
addImp(g, node(i, 0), neg(node(j, 1)));
}
}
}
// Solve SCC (Tarjan/Kosaraju)
// ... (Code SCC đầy đủ) ...
// Kiểm tra xem có biến nào i và not(i) cùng trong một SCC không
// return true nếu khả thi
return true; // Placeholder
}
int main() {
// Đọc input
// Binary Search trên câu trả lời
// In ra giá trị nhỏ nhất tìm được
}
- Time Complexity: O(N^2 log(MaxCoord^2))
- Space Complexity: O(N^2)
Đây là phương pháp chuẩn xác nhất. Ta binary search kết quả (giá trị bình phương khoảng cách tối đa cho phép M). Với mỗi giá trị M, ta xây dựng đồ thị 2-SAT để kiểm tra xem có cách gán (Ox hoặc Oy) cho mỗi cây hoa sao cho không có cặp nào vi phạm biên M hay không.
Mô hình 2-SAT:
- Mỗi cây hoa i có 2 biến: $xi$ (lên Ox) và $\neg xi$ (lên Oy).
- Với mỗi cặp (i, j), ta xét tất cả các khả năng:
- Cả 2 lên Ox: Khoảng cách $|xi - xj|^2$. Nếu $> M$ thì bỏ qua. Nếu $\le M$: Cấm $xi \land xj$.
- Cả 2 lên Oy: Khoảng cách $|yi - yj|^2$. Nếu $\le M$: Cấm $\neg xi \land \neg xj$.
- i lên Ox, j lên Oy: Khoảng cách $xi^2 + yj^2$. Nếu $\le M$: Cấm $xi \land \neg xj$.
- i lên Oy, j lên Ox: Khoảng cách $xj^2 + yi^2$. Nếu $\le M$: Cấm $\neg xi \land xj$.
Sau khi xây dựng đồ thị implicational, ta chạy Tarjan (hoặc Kosaraju) để tìm SCC. Nếu tồn tại biến $x$ và $\neg x$ nằm cùng SCC thì điều kiện M không khả thi.
Cách Giả lập (Simulation) với Binary Search
// Pseudocode for the logic
// Note: The provided solutions use 2-SAT, which is the correct optimal approach.
// The 'Simulation' idea mentioned in some scratch thoughts is flawed for general cases
// because it's greedy and doesn't guarantee global optimum.
// However, for N=20, we can do brute force.
// Brute Force for N <= 20 (mentioned in constraints)
#include <bits/stdc++.h>
using namespace std;
int main() {
int N; cin >> N;
vector<pair<long long, long long>> pts(N);
for(int i=0; i<N; i++) cin >> pts[i].first >> pts[i].second;
long long min_max_dist = LLONG_MAX;
// Try all 2^N assignments
for(int mask=0; mask < (1<<N); mask++) {
vector<long long> ox, oy;
for(int i=0; i<N; i++) {
if(mask & (1<<i)) ox.push_back(pts[i].first);
else oy.push_back(pts[i].second);
}
long long current_max = 0;
// Check distances
// 1. Points on OX
if(ox.size() > 1) {
sort(ox.begin(), ox.end());
long long d = (ox.back() - ox.front()) * (ox.back() - ox.front());
current_max = max(current_max, d);
}
// 2. Points on OY
if(oy.size() > 1) {
sort(oy.begin(), oy.end());
long long d = (oy.back() - oy.front()) * (oy.back() - oy.front());
current_max = max(current_max, d);
}
// 3. Cross pairs
for(long long x : ox) {
for(long long y : oy) {
long long d = x*x + y*y;
current_max = max(current_max, d);
}
}
if(current_max < min_max_dist) min_max_dist = current_max;
}
cout << min_max_dist << endl;
}
- Time Complexity: O(2^N * N^2) for N<=20, O(N^2 log...) for N<=200
- Space Complexity: O(N)
Với N nhỏ (N <= 20), ta có thể thử tất cả $2^N$ cách di chuyển. Với mỗi cách, ta tính bình phương khoảng cách lớn nhất giữa các cặp điểm và cập nhật kết quả tối thiểu. Đây là cách tiếp cận trực tiếp, dễ hiểu nhưng không khả thi với N lớn.
Đối với N lớn (<= 200), lời giải duy nhất khả thi và tối ưu là phương pháp 2-SAT kết hợp Binary Search.
Phân tích độ phức tạp
| Cách tiếp cận | Time | Space | Tên |
|---|---|---|---|
| 1 | O(N * Sum^2) | O(N * Sum) | Quy hoạch động dạng Knapsack (Tối ưu theo tổng bình phương) |
| 2 | O(N^2 log(MaxCoord^2)) | O(N^2) | Thăm dò biên (Binary Search) + 2-SAT |
| 3 | O(2^N * N^2) for N<=20, O(N^2 log...) for N<=200 | O(N) | Giả lập (Simulation) với Binary Search |
Bài học kinh nghiệm
- Bài toán có thể chuyển hóa thành bài toán 2-SAT: Tìm cách gán mỗi điểm về 1 trong 2 trục sao cho mọi cặp điểm đều có khoảng cách > M (với M là biên đang kiểm tra).
- Khoảng cách giữa hai điểm trên hai trục khác nhau là bình phương tọa độ (ví dụ $x^2 + y^2$), trong khi khoảng cách giữa hai điểm trên cùng trục là bình phương hiệu tọa độ.
- Sử dụng Binary Search trên giá trị bình phương khoảng cách (kết quả) kết hợp với kiểm tra tính khả thi của 2-SAT để tìm giá trị tối thiểu.
Lỗi thường gặp
- Lỗi tràn số (overflow) khi tính bình phương tọa độ lớn (lên tới $10^8$). Cần sử dụng loại dữ liệu lớn hơn (__int128 trong C++ hoặc chia đôi cách tính) khi tính bình phương trước khi so sánh.
- Nhầm lẫn giữa bài toán minimize tổng bình phương và minimize max bình phương. Sử dụng DP tích lũy tổng sẽ cho ra kết quả sai.
- Sai lệch trong việc xây dựng câu lệnh logic cho 2-SAT, đặc biệt là các điều kiện cấm (forbidden pairs).
Bình luận