Bài 25.5: Bài tập thực hành mảng 2 chiều trong C++

Bài 25.5: Bài tập thực hành mảng 2 chiều trong C++
Chào mừng các bạn quay trở lại với series C++ của FullhouseDev!
Sau khi đã tìm hiểu về lý thuyết và cách hoạt động cơ bản của mảng 2 chiều (mảng đa chiều) trong C++, giờ là lúc chúng ta xắn tay áo và bắt đầu thực hành. Lý thuyết dù vững đến đâu cũng cần được mài giũa qua các bài tập thực tế. Mảng 2 chiều là một cấu trúc dữ liệu cực kỳ quan trọng, đặc biệt khi làm việc với dữ liệu dạng bảng, ma trận, hoặc các bài toán cần biểu diễn không gian 2D.
Trong bài viết này, chúng ta sẽ cùng nhau giải quyết một số bài tập điển hình với mảng 2 chiều trong C++. Mục tiêu là giúp bạn nắm vững cách khai báo, khởi tạo, truy xuất, và thao tác với các phần tử trong mảng 2 chiều một cách thành thạo.
Hãy bắt đầu ngay thôi nào!
1. Khai báo, Khởi tạo và In Mảng 2 Chiều
Trước hết, chúng ta cần biết cách tạo ra một mảng 2 chiều và hiển thị nội dung của nó. Có nhiều cách để khai báo và khởi tạo, phổ biến nhất là dùng mảng tĩnh hoặc sử dụng vector
để có tính linh hoạt hơn về kích thước.
Ví dụ 1.1: Khai báo và Khởi tạo Mảng Tĩnh, rồi In ra
#include <iostream>
int main() {
// Khai báo và khởi tạo mảng 2 chiều tĩnh 3 hàng, 4 cột
int arr_static[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
cout << "--- Mang Tinh ---" << endl;
// Duyệt và in mảng
// Vòng lặp ngoài duyệt qua các hàng (row)
for (int i = 0; i < 3; ++i) {
// Vòng lặp trong duyệt qua các cột (column) trong hàng hiện tại
for (int j = 0; j < 4; ++j) {
// In phần tử tại vị trí (i, j)
cout << arr_static[i][j] << " ";
}
// Kết thúc một hàng, xuống dòng
cout << endl;
}
return 0;
}
Giải thích:
int arr_static[3][4];
: Khai báo một mảng 2 chiều có 3 hàng và 4 cột. Kích thước phải là hằng số khi khai báo mảng tĩnh.{ {1, ...}, ... };
: Cách khởi tạo trực tiếp các giá trị cho mảng khi khai báo.- Vòng lặp
for (int i = 0; i < 3; ++i)
: Duyệt qua chỉ số hàng, từ 0 đến 2. - Vòng lặp
for (int j = 0; j < 4; ++j)
: Duyệt qua chỉ số cột, từ 0 đến 3, cho mỗi hàng. arr_static[i][j]
: Truy xuất đến phần tử nằm ở hàng thứi
và cột thứj
.cout << arr_static[i][j] << " ";
: In giá trị của phần tử và thêm một khoảng trắng để phân tách.cout << endl;
: Sau khi in hết các phần tử của một hàng, xuống dòng để hàng tiếp theo hiển thị đúng cấu trúc 2 chiều.
Ví dụ 1.2: Khai báo và Khởi tạo Mảng Động bằng vector
, rồi In ra
#include <iostream>
#include <vector>
int main() {
// Kích thước mảng động (có thể do người dùng nhập)
int num_rows = 4;
int num_cols = 3;
// Khai báo và khởi tạo mảng 2 chiều động sử dụng vector
// vector<vector<int>> tạo ra một vector chứa các vector int
vector<vector<int>> arr_dynamic(num_rows, vector<int>(num_cols, 0)); // Khởi tạo với 0
// Gán một vài giá trị khác (tùy chọn)
arr_dynamic[1][2] = 99;
arr_dynamic[3][0] = 50;
cout << "--- Mang Dong (vector) ---" << endl;
// Duyệt và in mảng
// arr_dynamic.size() trả về số hàng
for (int i = 0; i < arr_dynamic.size(); ++i) {
// arr_dynamic[i].size() trả về số cột của hàng thứ i
for (int j = 0; j < arr_dynamic[i].size(); ++j) {
cout << arr_dynamic[i][j] << " ";
}
cout << endl;
}
return 0;
}
Giải thích:
vector<vector<int>> arr_dynamic;
: Khai báo một vector mà mỗi phần tử của nó lại là mộtvector<int>
, tạo nên cấu trúc 2 chiều.vector<vector<int>> arr_dynamic(num_rows, vector<int>(num_cols, 0));
: Cách khởi tạo nhanh một mảng 2 chiều kích thướcnum_rows
xnum_cols
với tất cả các phần tử ban đầu là 0.vector<int>(num_cols, 0)
tạo ra một vector 1 chiều cónum_cols
phần tử, tất cả bằng 0; sau đó,arr_dynamic(num_rows, ...)
tạo ranum_rows
bản sao của vector 1 chiều này.arr_dynamic.size()
: Lấy số lượng vector con (số hàng).arr_dynamic[i].size()
: Lấy kích thước của vector con thứi
(số cột của hàng thứi
). Với cách khởi tạo trên, số cột sẽ như nhau cho mọi hàng.- Cách truy xuất phần tử
arr_dynamic[i][j]
tương tự như mảng tĩnh.
Sử dụng vector<vector<int>>
được khuyến khích trong C++ hiện đại vì nó linh hoạt hơn nhiều so với mảng tĩnh, đặc biệt khi kích thước mảng không cố định lúc biên dịch.
2. Tính Tổng Các Phần Tử Trong Mảng
Một bài tập cơ bản là tính tổng tất cả các giá trị trong mảng 2 chiều.
Ví dụ 2.1: Tính Tổng Tất Cả Phần Tử
#include <iostream>
#include <vector>
int main() {
vector<vector<int>> matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
long long total_sum = 0; // Sử dụng long long để tránh tràn số nếu tổng lớn
// Duyệt qua từng phần tử và cộng vào tổng
for (int i = 0; i < matrix.size(); ++i) {
for (int j = 0; j < matrix[i].size(); ++j) {
total_sum += matrix[i][j];
}
}
cout << "Tong cua tat ca cac phan tu trong mang: " << total_sum << endl;
return 0;
}
Giải thích:
- Khởi tạo biến
total_sum
bằng 0. - Sử dụng cặp vòng lặp lồng nhau để duyệt qua tất cả các phần tử trong ma trận.
- Trong vòng lặp trong cùng,
total_sum += matrix[i][j];
cộng giá trị của phần tử hiện tại vào tổng. - Kiểu dữ liệu
long long
được sử dụng chototal_sum
để đảm bảo nó có thể chứa được tổng lớn, ngay cả khi các phần tử là số nguyên nhỏ.
3. Tìm Phần Tử Lớn Nhất và Nhỏ Nhất
Tìm giá trị lớn nhất và nhỏ nhất trong mảng 2 chiều cũng là một bài tập phổ biến.
Ví dụ 3.1: Tìm Giá Trị Lớn Nhất và Nhỏ Nhất
#include <iostream>
#include <vector>
#include <limits> // Cần cho numeric_limits
int main() {
vector<vector<int>> matrix = {
{10, -5, 20},
{0, 15, -10},
{30, 5, 25}
};
// Khởi tạo max_val với giá trị nhỏ nhất có thể của int
int max_val = numeric_limits<int>::min();
// Khởi tạo min_val với giá trị lớn nhất có thể của int
int min_val = numeric_limits<int>::max();
// Duyệt qua từng phần tử để so sánh và cập nhật
for (int i = 0; i < matrix.size(); ++i) {
for (int j = 0; j < matrix[i].size(); ++j) {
if (matrix[i][j] > max_val) {
max_val = matrix[i][j]; // Cập nhật nếu tìm thấy giá trị lớn hơn
}
if (matrix[i][j] < min_val) {
min_val = matrix[i][j]; // Cập nhật nếu tìm thấy giá trị nhỏ hơn
}
}
}
cout << "Phan tu lon nhat trong mang: " << max_val << endl;
cout << "Phan tu nho nhat trong mang: " << min_val << endl;
return 0;
}
Giải thích:
- Chúng ta khởi tạo
max_val
với giá trị nhỏ nhất có thể của kiểuint
(từnumeric_limits<int>::min()
) vàmin_val
với giá trị lớn nhất có thể (numeric_limits<int>::max()
). Điều này đảm bảo rằng phần tử đầu tiên (hoặc bất kỳ phần tử nào) của ma trận sẽ luôn lớn hơn hoặc bằngmax_val
ban đầu và nhỏ hơn hoặc bằngmin_val
ban đầu, từ đó khởi động quá trình so sánh đúng đắn. - Vòng lặp lồng nhau duyệt qua tất cả các phần tử.
- Trong vòng lặp trong cùng, chúng ta sử dụng câu lệnh
if
để kiểm tra xem phần tử hiện tại có lớn hơnmax_val
hay nhỏ hơnmin_val
hiện tại không. Nếu có, chúng ta cập nhậtmax_val
hoặcmin_val
.
Một cách khác để khởi tạo max_val
và min_val
là gán chúng bằng giá trị của phần tử đầu tiên của ma trận (matrix[0][0]
), sau đó duyệt qua các phần tử còn lại. Cách này cũng hiệu quả.
4. Tính Tổng Các Hàng và Các Cột
Bài tập này yêu cầu tính tổng riêng biệt cho từng hàng và từng cột của ma trận.
Ví dụ 4.1: Tính Tổng Từng Hàng
#include <iostream>
#include <vector>
int main() {
vector<vector<int>> matrix = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// Duyệt qua từng hàng
for (int i = 0; i < matrix.size(); ++i) {
int row_sum = 0; // Biến để lưu tổng của hàng hiện tại, reset cho mỗi hàng
// Duyệt qua từng phần tử trong hàng hiện tại
for (int j = 0; j < matrix[i].size(); ++j) {
row_sum += matrix[i][j];
}
cout << "Tong cua hang " << i << ": " << row_sum << endl;
}
return 0;
}
Giải thích:
- Vòng lặp ngoài duyệt qua các hàng (
i
). - Trước khi bắt đầu vòng lặp trong (duyệt cột) cho mỗi hàng, chúng ta khởi tạo
row_sum = 0;
. Biến này sẽ lưu tổng của hàngi
. - Vòng lặp trong duyệt qua các cột (
j
) của hàngi
. Trong vòng lặp này, chúng ta cộng giá trịmatrix[i][j]
vàorow_sum
. - Sau khi vòng lặp trong kết thúc (đã duyệt hết các cột của hàng
i
),row_sum
chứa tổng của hàng đó, và chúng ta in nó ra.
Ví dụ 4.2: Tính Tổng Từng Cột
#include <iostream>
#include <vector>
int main() {
vector<vector<int>> matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
{10, 11, 12}
};
// Giả định ma trận không rỗng và có số cột nhất quán
if (matrix.empty() || matrix[0].empty()) {
cout << "Ma tran rong." << endl;
return 1;
}
int num_rows = matrix.size();
int num_cols = matrix[0].size(); // Lấy số cột từ hàng đầu tiên
// Duyệt qua từng cột
for (int j = 0; j < num_cols; ++j) {
int col_sum = 0; // Biến để lưu tổng của cột hiện tại, reset cho mỗi cột
// Duyệt qua từng hàng trong cột hiện tại
for (int i = 0; i < num_rows; ++i) {
col_sum += matrix[i][j];
}
cout << "Tong cua cot " << j << ": " << col_sum << endl;
}
return 0;
}
Giải thích:
- Trong trường hợp tính tổng cột, vòng lặp ngoài duyệt qua các cột (
j
). - Trước khi bắt đầu vòng lặp trong (duyệt hàng) cho mỗi cột, chúng ta khởi tạo
col_sum = 0;
. Biến này sẽ lưu tổng của cộtj
. - Vòng lặp trong duyệt qua các hàng (
i
) cho cộtj
. Chú ý thứ tự chỉ số:matrix[i][j]
. - Sau khi vòng lặp trong kết thúc,
col_sum
chứa tổng của cột đó, và chúng ta in nó ra. - Lưu ý: Để tính tổng cột, chúng ta cần biết số lượng cột. Với
vector<vector<int>>
, số lượng cột có thể khác nhau giữa các hàng (tạo ra ma trận "răng cưa"). Tuy nhiên, với các ma trận hình chữ nhật thông thường, chúng ta có thể lấy số cột từ hàng đầu tiên:matrix[0].size()
. Cần thêm kiểm tra ma trận rỗng để tránh lỗi.
5. Tính Tổng Đường Chéo (Cho Ma Trận Vuông)
Bài tập này áp dụng cho ma trận vuông (số hàng bằng số cột). Chúng ta sẽ tính tổng đường chéo chính và đường chéo phụ.
Ví dụ 5.1: Tính Tổng Đường Chéo Chính và Phụ
#include <iostream>
#include <vector>
int main() {
vector<vector<int>> square_matrix = {
{ 1, 2, 3},
{ 4, 5, 6},
{ 7, 8, 9}
};
// Kiểm tra xem có phải ma trận vuông không và không rỗng
if (square_matrix.empty() || square_matrix.size() != square_matrix[0].size()) {
cout << "Day khong phai ma tran vuong hoac bi rong." << endl;
return 1;
}
int size = square_matrix.size(); // Kích thước (số hàng/cột)
int main_diag_sum = 0;
int anti_diag_sum = 0;
// Duyệt qua ma trận để tính tổng đường chéo
for (int i = 0; i < size; ++i) {
// Đường chéo chính: Chỉ số hàng và cột bằng nhau (i, i)
main_diag_sum += square_matrix[i][i];
// Đường chéo phụ: Tổng chỉ số hàng và cột bằng size - 1 (i, size - 1 - i)
anti_diag_sum += square_matrix[i][size - 1 - i];
}
cout << "Tong duong cheo chinh: " << main_diag_sum << endl;
cout << "Tong duong cheo phu: " << anti_diag_sum << endl;
return 0;
}
Giải thích:
- Kiểm tra điều kiện ma trận vuông: số hàng (
matrix.size()
) phải bằng số cột (matrix[0].size()
) và ma trận không được rỗng. - Đường chéo chính: Các phần tử nằm trên đường chéo chính có chỉ số hàng và cột bằng nhau:
[0][0]
,[1][1]
,[2][2]
, ...[i][i]
. Chúng ta chỉ cần một vòng lặp duy nhất duyệt từi = 0
đếnsize - 1
và cộngsquare_matrix[i][i]
vào tổng. - Đường chéo phụ: Các phần tử nằm trên đường chéo phụ có tổng chỉ số hàng và cột bằng
size - 1
:[0][size-1]
,[1][size-2]
,[2][size-3]
, ...[i][size - 1 - i]
. Tương tự, chỉ cần một vòng lặp duyệt từi = 0
đếnsize - 1
và cộngsquare_matrix[i][size - 1 - i]
vào tổng.
6. Tìm Kiếm Một Giá Trị Trong Mảng
Làm sao để biết liệu một giá trị cụ thể có tồn tại trong mảng 2 chiều hay không, và nếu có thì nó nằm ở vị trí nào?
Ví dụ 6.1: Tìm Kiếm Giá Trị
#include <iostream>
#include <vector>
int main() {
vector<vector<int>> matrix = {
{10, 20, 30},
{40, 50, 60},
{70, 80, 90}
};
int target = 50; // Giá trị cần tìm
bool found = false;
int found_row = -1, found_col = -1; // Lưu vị trí tìm thấy
// Duyệt qua từng phần tử để tìm kiếm
for (int i = 0; i < matrix.size(); ++i) {
for (int j = 0; j < matrix[i].size(); ++j) {
if (matrix[i][j] == target) {
found = true;
found_row = i;
found_col = j;
// break; // Có thể thêm break ở đây nếu chỉ cần tìm vị trí đầu tiên
}
}
// if (found) break; // Có thể thêm break ở đây nếu chỉ cần tìm vị trí đầu tiên
}
if (found) {
cout << "Gia tri " << target << " duoc tim thay tai vi tri ("
<< found_row << ", " << found_col << ")" << endl;
} else {
cout << "Gia tri " << target << " khong tim thay trong mang." << endl;
}
return 0;
}
Giải thích:
- Chúng ta sử dụng một biến boolean
found
để đánh dấu xem giá trị đã được tìm thấy hay chưa, ban đầu làfalse
. - Hai biến
found_row
vàfound_col
được dùng để lưu lại chỉ số hàng và cột của phần tử tìm thấy. Khởi tạo chúng với giá trị không hợp lệ (-1). - Duyệt qua toàn bộ ma trận bằng vòng lặp lồng nhau.
- Bên trong vòng lặp, so sánh phần tử hiện tại
matrix[i][j]
vớitarget
. - Nếu bằng, đặt
found = true
và lưu lại chỉ sối
vàj
. - (Tùy chọn): Nếu chỉ cần tìm vị trí xuất hiện đầu tiên, có thể sử dụng
break
bên trong vòng lặpj
để thoát khỏi vòng lặp trong ngay khi tìm thấy, và thêmbreak
bên ngoài vòng lặpj
(nhưng vẫn trong vòng lặpi
) để thoát khỏi vòng lặp ngoài. - Sau khi duyệt xong, kiểm tra giá trị của
found
để thông báo kết quả.
7. Chuyển Vị Ma Trận
Chuyển vị (transpose) một ma trận là thao tác biến các hàng thành cột và các cột thành hàng. Nếu ma trận gốc có kích thước M x N, ma trận chuyển vị sẽ có kích thước N x M.
Ví dụ 7.1: Chuyển Vị Ma Trận
#include <iostream>
#include <vector>
int main() {
vector<vector<int>> original_matrix = {
{1, 2, 3},
{4, 5, 6}
}; // Ma trận 2x3
if (original_matrix.empty()) {
cout << "Ma tran goc rong, khong the chuyen vi." << endl;
return 1;
}
int num_rows = original_matrix.size();
int num_cols = original_matrix[0].size(); // Giả định các hàng có cùng số cột
// Tạo ma trận chuyển vị với kích thước nguoc lai: num_cols x num_rows
vector<vector<int>> transposed_matrix(num_cols, vector<int>(num_rows));
// Thực hiện chuyển vị
// Duyệt qua ma trận gốc
for (int i = 0; i < num_rows; ++i) {
for (int j = 0; j < num_cols; ++j) {
// Phần tử (i, j) của ma trận gốc trở thành phần tử (j, i) của ma trận chuyển vị
transposed_matrix[j][i] = original_matrix[i][j];
}
}
cout << "--- Ma Tran Goc ---" << endl;
for (int i = 0; i < num_rows; ++i) {
for (int j = 0; j < num_cols; ++j) {
cout << original_matrix[i][j] << " ";
}
cout << endl;
}
cout << "\n--- Ma Tran Chuyen Vi ---" << endl;
for (int i = 0; i < num_cols; ++i) { // Chú ý duyệt theo kích thước mới: num_cols x num_rows
for (int j = 0; j < num_rows; ++j) {
cout << transposed_matrix[i][j] << " ";
}
cout << endl;
}
return 0;
}
Giải thích:
- Xác định kích thước của ma trận gốc (
num_rows
vànum_cols
). - Tạo một ma trận mới,
transposed_matrix
, với kích thước ngược lại:num_cols
hàng vànum_rows
cột. - Duyệt qua ma trận gốc bằng các chỉ số
i
(hàng gốc) vàj
(cột gốc). - Với mỗi phần tử
original_matrix[i][j]
, gán nó vào vị trí[j][i]
trongtransposed_matrix
. Đây chính là lõi của thao tác chuyển vị: hoán đổi chỉ số hàng và cột. - In cả ma trận gốc và ma trận chuyển vị để thấy kết quả. Chú ý vòng lặp khi in ma trận chuyển vị phải duyệt theo kích thước mới của nó (
num_cols
hàng vànum_rows
cột).
Hy vọng qua các ví dụ thực hành này, bạn đã cảm thấy tự tin hơn khi làm việc với mảng 2 chiều trong C++. Việc lặp đi lặp lại các mẫu duyệt mảng lồng nhau cho các mục đích khác nhau (tính tổng, tìm kiếm, in ấn,...) là cách tốt nhất để nắm vững cấu trúc dữ liệu này.
Hãy thử tự mình biến đổi các ví dụ này, ví dụ như:
- Thêm chức năng nhập dữ liệu từ bàn phím vào mảng 2 chiều.
- Viết hàm riêng cho từng thao tác (in, tính tổng, tìm max/min,...).
- Thử nghiệm với các kiểu dữ liệu khác ngoài
int
. - Áp dụng mảng 2 chiều để giải quyết các bài toán khác (ví dụ: ma trận kề trong đồ thị, bảng trò chơi cờ caro đơn giản,...).
Bài tập ví dụ: C++ Bài 11.A5: Tấm lưới trắng đen
Chúng ta có một lưới với \(2\) hàng ngang và \(2\) cột dọc. Mỗi ô vuông là màu đen hoặc trắng, và có ít nhất \(2\) ô vuông màu đen. Màu của các ô vuông được cho dưới dạng các chuỗi \(S_1\) và \(S_2\), như sau:
Nếu ký tự thứ \(j\) của \(S_i\) là \(#\), ô vuông ở hàng thứ \(i\) từ trên xuống và cột thứ \(j\) từ trái sang là màu đen. Nếu ký tự thứ \(j\) của \(S_i\) là \(.\), ô vuông ở hàng thứ \(i\) từ trên xuống và cột thứ \(j\) từ trái sang là màu trắng. Bạn có thể di chuyển giữa hai ô vuông màu đen khác nhau nếu và chỉ nếu chúng có chung một cạnh. Xác định xem có thể di chuyển từ mỗi ô vuông màu đen đến mỗi ô vuông màu đen khác (trực tiếp hoặc gián tiếp) chỉ bằng cách đi qua các ô vuông màu đen hay không.
Ràng buộc
Mỗi chuỗi \(S_1\) và \(S_2\) là một chuỗi có hai ký tự gồm \(#\) và \(.\) \(S_1\) và \(S_2\) có tổng cộng hai hoặc nhiều hơn \(#\).
Đầu vào
Đầu vào được cung cấp từ Standard Input theo định dạng sau:
\(S_1\)
\(S_2\)
Đầu ra
Nếu có thể di chuyển từ mỗi ô vuông màu đen đến mỗi ô vuông màu đen, in Yes; nếu không, in No.
Ví dụ:
Ví dụ 1
Đầu vào
##
.#
Đầu ra
Yes
Có thể di chuyển trực tiếp giữa các ô vuông màu đen ở góc trên bên trái và góc trên bên phải và giữa góc trên bên phải và góc dưới bên phải. Hai nước đi này cho phép chúng ta di chuyển từ mỗi ô vuông màu đen đến mỗi ô vuông màu đen, vì vậy câu trả lời là Yes.
Ví dụ 2
Đầu vào
.#
#.
Đầu ra
No
Không thể di chuyển giữa các ô vuông màu đen ở góc trên bên phải và góc dưới bên trái, vì vậy câu trả lời là No.
Giải thích ví dụ mẫu
Ví dụ 1
Input:
##
.#
Giải thích:
- Các ô màu đen đều liên kết với nhau qua các cạnh, vì vậy có thể di chuyển từ bất kỳ ô màu đen nào đến bất kỳ ô màu đen nào khác.
Ví dụ 2
Input:
.#
#.
Giải thích:
- Các ô màu đen không liên kết với nhau, vì vậy không thể di chuyển từ ô màu đen này đến ô màu đen kia.
<br>
1. Phân tích bài toán:
- Chúng ta có một lưới kích thước rất nhỏ: 2 hàng x 2 cột.
- Mỗi ô có thể là đen (
#
) hoặc trắng (.
). - Có ít nhất 2 ô màu đen.
- Di chuyển chỉ được thực hiện giữa hai ô màu đen kề cạnh (chung một cạnh).
- Cần xác định xem có thể đi từ bất kỳ ô màu đen nào đến bất kỳ ô màu đen khác hay không, chỉ qua các ô màu đen kề cạnh.
2. Quan sát lưới 2x2:
Lưới 2x2 chỉ có 4 ô. Các cặp ô kề cạnh là:
- (Hàng 1, Cột 1) và (Hàng 1, Cột 2)
- (Hàng 1, Cột 1) và (Hàng 2, Cột 1)
- (Hàng 1, Cột 2) và (Hàng 2, Cột 2)
- (Hàng 2, Cột 1) và (Hàng 2, Cột 2)
Các cặp ô không kề cạnh (chéo nhau) là:
- (Hàng 1, Cột 1) và (Hàng 2, Cột 2)
- (Hàng 1, Cột 2) và (Hàng 2, Cột 1)
3. Xác định điều kiện kết nối:
Bài toán hỏi liệu tất cả các ô màu đen có tạo thành một "thành phần liên thông" (connected component) duy nhất hay không. Với lưới nhỏ 2x2, điều này có thể được suy luận trực tiếp dựa trên vị trí của các ô màu đen.
Hãy xem xét các trường hợp về số lượng ô màu đen (biết rằng có ít nhất 2 ô đen):
Trường hợp 1: Có 4 ô màu đen.
## ##
Tất cả các ô đều đen. Mỗi ô đen đều kề cạnh với ít nhất hai ô đen khác. Rõ ràng là có thể di chuyển giữa mọi cặp ô đen. Kết quả: Yes.
Trường hợp 2: Có 3 ô màu đen. Ba ô đen trong lưới 2x2 sẽ luôn tạo thành hình chữ L (hoặc xoay/lật). Ví dụ:
## hoặc .# hoặc # . hoặc . # #. ## ## ##
Trong tất cả các trường hợp này, ô đen ở "góc" của chữ L kề cạnh với hai ô đen còn lại. Như vậy, ba ô đen này luôn liên thông với nhau. Kết quả: Yes.
Trường hợp 3: Có 2 ô màu đen.
- Nếu hai ô đen kề cạnh nhau: Chúng liên thông trực tiếp. Do chỉ có 2 ô đen, nên chúng ta có thể đi từ ô này sang ô kia. Kết quả: Yes. (Ví dụ:
##\n..
hoặc.#\n.#
) - Nếu hai ô đen không kề cạnh nhau (tức là chúng chéo nhau):
Hai ô đen này không có đường đi trực tiếp hoặc gián tiếp (vì không có ô đen nào khác ở giữa để làm cầu nối). Chúng hoàn toàn tách biệt. Kết quả: No.# . hoặc . # . # # .
- Nếu hai ô đen kề cạnh nhau: Chúng liên thông trực tiếp. Do chỉ có 2 ô đen, nên chúng ta có thể đi từ ô này sang ô kia. Kết quả: Yes. (Ví dụ:
4. Rút gọn logic:
Từ phân tích trên, ta thấy rằng chỉ có một trường hợp duy nhất trả về "No": đó là khi chỉ có đúng 2 ô màu đen và chúng nằm ở vị trí chéo nhau. Tất cả các trường hợp còn lại (4 ô đen, 3 ô đen, hoặc 2 ô đen kề cạnh) đều trả về "Yes".
Vậy, logic đơn giản là: Kiểm tra xem cấu hình các ô màu đen có phải là một trong hai trường hợp đường chéo tách biệt hay không. Nếu đúng, in "No". Ngược lại, in "Yes".
Hai cấu hình chéo tách biệt đó là:
- Ô (Hàng 1, Cột 1) là
#
, Ô (Hàng 1, Cột 2) là.
, Ô (Hàng 2, Cột 1) là.
, Ô (Hàng 2, Cột 2) là#
. - Ô (Hàng 1, Cột 1) là
.
, Ô (Hàng 1, Cột 2) là#
, Ô (Hàng 2, Cột 1) là#
, Ô (Hàng 2, Cột 2) là.
.
Dựa vào cách đầu vào được cung cấp (S1
và S2
), hai cấu hình này tương ứng với:
S1
là"#."
vàS2
là".#"
S1
là".#"
vàS2
là"#."
5. Hướng dẫn triển khai bằng C++:
- Bao gồm các thư viện cần thiết, chủ yếu là
<iostream>
cho nhập/xuất và<string>
để xử lý chuỗi. - Sử dụng
cin
để đọc hai chuỗiS1
vàS2
từ đầu vào chuẩn. - Sử dụng một câu lệnh điều kiện
if
để kiểm tra xem cặp chuỗiS1
vàS2
có khớp với một trong hai cấu hình "No" đã xác định ở bước 4 hay không. Bạn có thể dùng toán tử logic&&
và||
để kết hợp các điều kiện so sánh chuỗi. - Nếu điều kiện trong
if
đúng, sử dụngcout
để in ra "No" và xuống dòng. - Ngược lại (trong phần
else
), sử dụngcout
để in ra "Yes" và xuống dòng. - Hàm
main
cần trả về 0.
Gợi ý sử dụng std:
string
để lưu trữS1
vàS2
.cin
vàcout
cho nhập xuất.- So sánh chuỗi trực tiếp bằng toán tử
==
.
Comments