Hướng dẫn giải của Trò chơi
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 tìm ra số lượng câu trả lời 'even' (chẵn) trong dãy N bit (0 hoặc 1) sao cho không mâu thuẫn với các câu hỏi và câu trả lời đã cho. Cụ thể, có K câu hỏi, mỗi câu hỏi chỉ một đoạn từ i đến j và trả lời 'odd' (lẻ) hoặc 'even' (chẵn) số lượng bit 1 trong đoạn đó. Tuy nhiên, chỉ có một câu trả lời là đúng, còn lại là sai. Ta cần xác định số lượng câu trả lời 'even'.
Để giải quyết, ta nhận thấy:
- Xét tổng số 1 trong toàn dãy là S. Nếu S chẵn, toàn bộ câu trả lời 'even' là đúng. Nếu S lẻ, chỉ có các câu trả lời 'odd' là đúng.
- Xét một câu hỏi về đoạn [i, j]. Gọi P[x] là tổng số 1 từ 1 đến x. Số 1 trong đoạn [i, j] là P[j] - P[i-1].
- Nếu câu trả lời là 'even', ta có P[j] - P[i-1] ≡ 0 (mod 2) => P[j] ≡ P[i-1] (mod 2).
- Nếu câu trả lời là 'odd', ta có P[j] - P[i-1] ≡ 1 (mod 2) => P[j] ≠ P[i-1] (mod 2).
Vấn đề trở thành bài toán tìm xem có thể gán giá trị chẵn/lẻ cho các mốc P[0], P[1], ..., P[N] sao cho vừa khớp với các ràng buộc từ các câu hỏi hay không. Tuy nhiên, ta không cần tìm chính xác cách gán, mà chỉ cần đếm số câu trả lời 'even'.
Giả sử có một cách gán hợp lệ. Khi đó, tổng S = P[N] - P[0] = P[N] (vì P[0]=0).
- Nếu P[N] chẵn, ta có thể gán P[0]=0, P[N]=0. Các câu hỏi 'even' (P[j]=P[i-1]) sẽ được thỏa mãn.
- Nếu P[N] lẻ, ta có thể gán P[0]=0, P[N]=1. Các câu hỏi 'odd' (P[j]≠P[i-1]) sẽ được thỏa mãn.
Vậy, ta chỉ cần kiểm tra:
- Liệu có thể gán các P[x] sao cho P[N]=0 và tất cả ràng buộc 'even' được thỏa mãn không? Nếu có, số câu trả lời 'even' đúng là toàn bộ.
- Liệu có thể gán các P[x] sao cho P[N]=1 và tất cả ràng buộc 'odd' được thỏa mãn không? Nếu có, số câu trả lời 'odd' đúng là toàn bộ.
Nếu trường hợp 1 đúng, đáp án là K (số câu trả lời 'even'). Nếu trường hợp 2 đúng, đáp án là 0. Nếu cả hai đều đúng (có thể xảy ra khi K=0), đáp án có thể là K hoặc 0, nhưng theo logic bài toán, ta cần tìm số câu trả lời 'even' chưa mâu thuẫn, thường là K.
Các cách tiếp cận
Cách DSU (Disjoint Set Union) - Theo dõi quan hệ chẵn lẻ
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 20005; // 2*N do có thể chênh lệch chỉ số
int parent[MAXN];
int diff[MAXN]; // diff[v] = P[v] xor P[parent[v]]
int n, k;
int find(int u) {
if (parent[u] == u) return u;
int root = find(parent[u]);
diff[u] ^= diff[parent[u]]; // Path compression
return parent[u] = root;
}
bool merge(int u, int v, int w) { // w = 0 if P[u] == P[v], 1 if P[u] != P[v]
int root_u = find(u);
int root_v = find(v);
if (root_u == root_v) {
// Check consistency
return (diff[u] ^ diff[v]) == w;
}
parent[root_u] = root_v;
// diff[root_u] ^ diff[v] ^ diff[u] = w (cần P[root_u] xor P[root_v] = w)
// P[root_u] xor P[u] = diff[u] => P[root_u] = P[u] xor diff[u]
// P[root_v] xor P[v] = diff[v] => P[root_v] = P[v] xor diff[v]
// P[root_u] xor P[root_v] = w
// (P[u] xor diff[u]) xor (P[v] xor diff[v]) = w
// P[u] xor P[v] = w xor diff[u] xor diff[v]
// diff[root_u] = P[root_u] xor P[root_v] = w xor diff[u] xor diff[v]
diff[root_u] = w ^ diff[u] ^ diff[v];
return true;
}
bool check(int target_pn, vector<tuple<int,int,int>>& queries, int type) {
// type 0: check if we can satisfy even constraints with P[N] = target_pn
// type 1: check if we can satisfy odd constraints with P[N] = target_pn
// Actually, we just need to check disjoint sets for consistency.
// But we must consider P[0] and P[N].
// P[0] = 0.
// P[N] = target_pn.
// So we add constraint P[0] ^ P[N] = target_pn.
// Also, we only add constraints of the specific type.
// Reset DSU
for(int i=0; i<=2*n; ++i) {
parent[i] = i;
diff[i] = 0;
}
// Add base constraint P[0] vs P[N]
if (!merge(0, N, target_pn)) return false;
for (auto& q : queries) {
int u = get<0>(q);
int v = get<1>(q);
int w = get<2>(q);
if (w == type) {
if (!merge(u, v, (type == 0 ? 0 : 1))) return false;
}
}
return true;
}
int main() {
cin >> n >> k;
vector<tuple<int, int, int>> queries;
int even_count = 0;
for (int i = 0; i < k; ++i) {
int u, v;
string s;
cin >> u >> v >> s;
// Map: P[u-1] vs P[v]
// DSU indices: 0 to N
// Let's use u-1 and v for P indices
queries.push_back({u-1, v, (s == "odd" ? 1 : 0)});
if (s == "even") even_count++;
}
// Case 1: P[N] = 0 (Total sum is even)
// Then all 'even' queries must be true (P[v] == P[u-1] => w=0)
// All 'odd' queries must be false (P[v] != P[u-1] => w=1)
// Wait, if P[N]=0, we don't need to satisfy 'odd' queries?
// The problem says: "find the last answer that doesn't contradict"
// If P[N]=0, then all 'even' answers are correct.
// So we only need to check if 'even' constraints are consistent.
// If they are, then answer is total 'even' count.
// Case 2: P[N] = 1 (Total sum is odd)
// Then all 'odd' answers are correct.
// So we only need to check if 'odd' constraints are consistent.
// If they are, answer is 0.
// Actually, the logic is:
// We need to find a valid assignment of P values.
// If P[N] = 0, then all 'even' queries must be satisfied. 'Odd' queries can be unsatisfied.
// If P[N] = 1, then all 'odd' queries must be satisfied.
// Check Case 1: P[N] = 0
bool possible_even = true;
// Reset and build graph with 'even' constraints + P[0]=0, P[N]=0
for(int i=0; i<=2*n; ++i) { parent[i]=i; diff[i]=0; }
if (!merge(0, n, 0)) possible_even = false;
for (auto& q : queries) {
int u = get<0>(q);
int v = get<1>(q);
int w = get<2>(q);
if (w == 0) { // even constraint
if (!merge(u, v, 0)) { possible_even = false; break; }
}
}
if (possible_even) {
cout << even_count << endl;
return 0;
}
// Check Case 2: P[N] = 1
bool possible_odd = true;
for(int i=0; i<=2*n; ++i) { parent[i]=i; diff[i]=0; }
if (!merge(0, n, 1)) possible_odd = false;
for (auto& q : queries) {
int u = get<0>(q);
int v = get<1>(q);
int w = get<2>(q);
if (w == 1) { // odd constraint
if (!merge(u, v, 1)) { possible_odd = false; break; }
}
}
if (possible_odd) {
cout << 0 << endl;
} else {
// Problem implies there is a solution.
// If neither works, something is wrong or K=0.
cout << even_count << endl; // Default
}
return 0;
}
- Time Complexity: O(K * α(N))
- Space Complexity: O(N)
Phương pháp sử dụng DSU (Disjoint Set Union) để quản lý các quan hệ chẵn/lẻ giữa các tổng tiền tố P[i].
- Mô hình hóa:
- Mảng P: P[i] là tổng số 1 từ 1 đến i. P[0] = 0.
- Câu hỏi [i, j] 'even': P[j] - P[i-1] chẵn => P[j] ≡ P[i-1] (mod 2) => P[j] XOR P[i-1] = 0.
- Câu hỏi [i, j] 'odd': P[j] - P[i-1] lẻ => P[j] ≠ P[i-1] (mod 2) => P[j] XOR P[i-1] = 1.
- DSU với trọng số:
- Ta cần một DSU có thể lưu trữ quan hệ XOR giữa các node.
parent[u]: node cha của u.diff[u]: giá trị P[u] XOR P[parent[u]].- Khi
find(u), ta cần updatediff[u]để nó lưu P[u] XOR P[root]. - Khi
merge(u, v, w)(với w là P[u] XOR P[v]), ta nối root của u vào root của v và tínhdiff[root_u].
- Hai trường hợp:
- Trường hợp 1 (Tổng số 1 chẵn): P[N] = 0. Khi đó, tất cả câu trả lời 'even' đều đúng. Ta chỉ cần kiểm tra xem các ràng buộc 'even' có mâu thuẫn không. Ta thêm ràng buộc P[0]=0, P[N]=0 (tức P[N] XOR P[0] = 0) và xử lý các ràng buộc 'even'. Nếu hợp lệ, đáp án là số lượng câu trả lời 'even'.
- Trường hợp 2 (Tổng số 1 lẻ): P[N] = 1. Khi đó, tất cả câu trả lời 'odd' đều đúng. Ta chỉ cần kiểm tra các ràng buộc 'odd'. Ta thêm ràng buộc P[N] XOR P[0] = 1 và xử lý các ràng buộc 'odd'. Nếu hợp lệ, đáp án là 0.
- Kết luận: Nếu trường hợp 1 hợp lệ, in ra K (số 'even'). Ngược lại, in ra 0. (Theo đề bài, luôn có nghiệm).
Cách Optimized DSU (Minified)
#include <bits/stdc++.h>
using namespace std;
int parent[20005], diff[20005];
int find(int u) {
if (parent[u] == u) return u;
int r = find(parent[u]);
diff[u] ^= diff[parent[u]];
return parent[u] = r;
}
bool unite(int u, int v, int w) {
int ru = find(u), rv = find(v);
if (ru == rv) return (diff[u] ^ diff[v]) == w;
parent[ru] = rv;
diff[ru] = w ^ diff[u] ^ diff[v];
return true;
}
int main() {
ios_base::sync_with_stdio(0); cin.tie(0);
int n, k; cin >> n >> k;
vector<tuple<int, int, int>> qs;
int cnt_even = 0;
for (int i = 0; i < k; ++i) {
int u, v; string s; cin >> u >> v >> s;
int w = (s == "odd");
qs.push_back({u - 1, v, w});
if (s == "even") cnt_even++;
}
// Check if P[N] = 0 is possible (All 'even' answers correct)
bool ok0 = true;
for (int i = 0; i <= 2 * n; ++i) parent[i] = i, diff[i] = 0;
if (!unite(0, n, 0)) ok0 = false;
for (auto& q : qs) {
if (get<2>(q) == 0 && !unite(get<0>(q), get<1>(q), 0)) { ok0 = false; break; }
}
if (ok0) { cout << cnt_even; return 0; }
// Check if P[N] = 1 is possible (All 'odd' answers correct)
for (int i = 0; i <= 2 * n; ++i) parent[i] = i, diff[i] = 0;
if (!unite(0, n, 1)) return 0;
for (auto& q : qs) {
if (get<2>(q) == 1 && !unite(get<0>(q), get<1>(q), 1)) { cout << 0; return 0; }
}
cout << 0;
return 0;
}
- Time Complexity: O(K * α(N))
- Space Complexity: O(N)
Đây là phiên bản tối ưu hóa của phương pháp DSU. Thay vì tách biệt hoàn toàn hai trường hợp trong code dài dòng, ta có thể viết gọn logic.
- Mảng
parentvàdiffđược khai báo toàn cục. - Hàm
findvàuniteđược viết ngắn gọn, thực hiện chuẩn xác việc path compression và cập nhật trọng số XOR. - Logic kiểm tra hai trường hợp được giữ nguyên:
- Thử gán P[N] = 0, kiểm tra các ràng buộc 'even'. Nếu ổn, in ra số 'even'.
- Nếu không, thử gán P[N] = 1, kiểm tra các ràng buộc 'odd'. Nếu ổn, in ra 0.
Phương pháp này về bản chất là giống Solution 1 và 2 nhưng code sạch sẽ và dễ hiểu hơn.
Phân tích độ phức tạp
| Cách tiếp cận | Time | Space | Tên |
|---|---|---|---|
| 1 | O(K * α(N)) | O(N) | DSU (Disjoint Set Union) - Theo dõi quan hệ chẵn lẻ |
| 2 | O(K * α(N)) | O(N) | Optimized DSU (Minified) |
Bài học kinh nghiệm
- Phân tích tính chẵn lẻ của tổng số 1 trong đoạn [i, j] dựa trên tổng tiền tố P[j] - P[i-1].
- Bài toán có thể chia thành 2 trường hợp độc lập: tổng số 1 toàn dãy chẵn hoặc lẻ.
- DSU With Offsets (Weighted DSU) là thuật toán chuẩn để giải quyết các bài toán quan hệ XOR/Modulo 2.
Lỗi thường gặp
- Sử dụng sai chỉ số mảng (1-based vs 0-based) khi chuyển đổi từ câu hỏi [i, j] sang quan hệ giữa các tổng tiền tố.
- Không xử lý path compression đúng cách trong DSU có trọng số.
- Lầm tưởng rằng phải tìm ra chính xác cách gán giá trị, trong khi chỉ cần kiểm tra tính khả thi của từng trường hợp chẵn/lẻ của tổng.
Bình luận