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ột vector<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ước num_rows x num_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 ra num_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 cho total_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ểu int (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ằng max_val ban đầu và nhỏ hơn hoặc bằng min_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ơn max_val hay nhỏ hơn min_val hiện tại không. Nếu có, chúng ta cập nhật max_val hoặc min_val.

Một cách khác để khởi tạo max_valmin_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àng i.
  • Vòng lặp trong duyệt qua các cột (j) của hàng i. Trong vòng lặp này, chúng ta cộng giá trị matrix[i][j] vào row_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ột j.
  • Vòng lặp trong duyệt qua các hàng (i) cho cột j. 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 đến size - 1 và cộng square_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 đến size - 1 và cộng square_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_rowfound_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ới target.
  • Nếu bằng, đặt found = true và lưu lại chỉ số ij.
  • (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ặp j để thoát khỏi vòng lặp trong ngay khi tìm thấy, và thêm break bên ngoài vòng lặp j (nhưng vẫn trong vòng lặp i) để 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_rowsnum_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] trong transposed_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):
      # .  hoặc  . #
      . #        # .
      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.

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 (S1S2), hai cấu hình này tương ứng với:

  • S1"#."S2".#"
  • S1".#"S2"#."

5. Hướng dẫn triển khai bằng C++:

  1. 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.
  2. Sử dụng cin để đọc hai chuỗi S1S2 từ đầu vào chuẩn.
  3. Sử dụng một câu lệnh điều kiện if để kiểm tra xem cặp chuỗi S1S2 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 &&|| để kết hợp các điều kiện so sánh chuỗi.
  4. Nếu điều kiện trong if đúng, sử dụng cout để in ra "No" và xuống dòng.
  5. Ngược lại (trong phần else), sử dụng cout để in ra "Yes" và xuống dòng.
  6. Hàm main cần trả về 0.

Gợi ý sử dụng std:

  • string để lưu trữ S1S2.
  • cincout cho nhập xuất.
  • So sánh chuỗi trực tiếp bằng toán tử ==.

Làm thêm nhiều bài tập miễn phí tại đây

Comments

There are no comments at the moment.