Latin Squares

Trong dự án dài hơi về TAoCP, những phần đầu của Vol 4A.

Định nghĩa

Cho một ma trận vuông có kích thước M và các phần tử thuộc 0,1,,M1, ta xây dựng ma trận sao cho mỗi phần tử i trong tập 0,1,,M1 chỉ xuất hiện đúng 1 lần trong mỗi cột và mỗi dòng.

Ví dụ

Cho 16 quân bài bao gồm tổ hợp của các quân J, K, Q, A và 4 loại ,,,, ta sắp xếp sao cho mỗi dòng/​cột có đủ 4 chất và 4 quân (Jacques Ozaman, math­e­ma­tiques et physiques, Paris 1725).

Ứng dụng

Thực ra trong toán học đôi khi ta không cần một ứng dụng thực tế của một vấn đề để có động lực nghiên cứu hay tìm hiểu. Tuy nhiên, mình lại là một thanh niên học khoa học máy tính với thiên hướng engineer” hơn là các nhà toán học cao siêu, nên mình đòi hỏi cái mình học có tính ứng dụng ít nhiều. Về khối vuông Latin này có kha khá ứng dụng:

  • Sudoku là một dạng của khối vuông Latin, và trò chơi này trở thành một trong những trò chơi giải đố nổi tiếng nhất thế giới.
  • Trong cài đặt thí nghiệm.
  • Mã sửa lỗi.
  • Mã hóa.

Câu chuyện lịch sử

Năm 1779, một câu đố tương tự như trong phần ví dụ đã thu hút nhà toán học vĩ đại: Leonhard Euler

Thirty-six of­fi­cers of six dif­fer­ent ranks, taken from six dif­fer­ent reg­i­ments, want to march in a 6×6 for­ma­tion so that each row and each col­umn will con­tain one of­fi­cer of each rank and one of each reg­i­ment. How can they do it?

Dù đã gần như mù nhưng Euler vẫn quyết tâm giải bài toán này và tổng quát hóa cho các trường hợp M=1,2,. Tuy nhiên, với Mmod4=2 khiến ông không giải được, Sau đó ông đã đưa ra một con­jec­ture rằng không thể giải câu đố này với trường hợp tổng quát Mmod4=2.

Tuy nhiên phần định nghĩa về khối Latin hơi khác 1 chút so với phần ví dụ. Nếu ta đặt =1,=2,=3,=4, và tách chất và quân bài ra 2 ma trận, ta được 2 ma trận Latin.

[JAKQQKAJAJQKKQJA]

[2143432134121234]

Figure 1 trở thành

[J2A1K4Q3Q4K3A2J1A3J4Q1K2K1Q2J3A4]

Hai ma trận được gọi là or­thog­o­nal khi chập các phần tử lại với nhau, mỗi phần tử trong ma trận mới là duy nhất. Bài toán lúc đó Euler phải giải là: cho 1 ma trận Latin, làm sao để tìm được một ma trận Latin khác or­thog­o­nal với nó. Ma trận sau khi được chập lại gọi là Graeco-Latin. Để chứng minh nhận định của Euler đúng hay sai, các nhà toán học đã mất gần 180 năm với sự giúp đỡ của máy tính.

Năm 1957, Paige và Tompkins đã thử lập trình trên máy SWAC để tìm ví dụ phản chứng khi M=10 nhưng sau gần 5 giờ chạy liên tục không ra kết quả, họ đã tắt chương trình.

Năm 1960, Parker và đồng sự đã chứng minh rằng có thể tìm được 1 ma trận or­thog­o­nal với M6 và phản chứng nhận định của Euler. Đồng thời, ông cũng viết 1 chương trình trên máy UNIVAC 1206 để sinh ra ma trận đó. Chương trình chưa đến 1 giờ để có thể tìm ra lời giải với cùng in­put năm 1957:

0 1 2 3 4 5 6 7 8 9
1 8 3 2 5 4 7 6 9 0
2 9 5 6 3 0 8 4 7 1
3 7 0 9 8 6 1 5 2 4
4 6 7 5 2 9 0 8 1 3
5 0 9 4 7 8 3 1 6 2
6 5 4 7 1 3 2 9 0 8
7 4 1 8 0 2 9 3 5 6
8 3 6 0 9 1 5 2 4 7
9 2 8 1 6 7 4 0 3 5

Một so­lu­tion trong sách:

0 2 8 5 9 4 7 3 6 1
1 7 4 9 3 6 5 0 2 8
2 5 6 4 8 7 0 1 9 3
3 6 9 0 4 5 8 2 1 7
4 8 1 7 5 3 6 9 0 2
5 1 7 8 0 2 9 4 3 6
6 9 0 2 7 1 3 8 4 5
7 3 5 1 2 0 4 6 8 9
8 0 2 3 6 9 1 7 5 4
9 4 3 6 1 8 2 5 7 0

Transversals

Parker sử dụng lại khái niệm trans­ver­sal mà trước đây Euler từng định nghĩa, trans­ver­sal là cách để chọn ra chuỗi M phần tử sao cho chỉ có 1 phần tử duy nhất ở mỗi dòng, mỗi cột và duy nhất trong trans­ver­sal.

Ví dụ: 0859734216, tức ta chọn 0 ở cột 1, 8 ở cột 2, 5 ở cột 3, … 6 ở cột 10. Giả sử ta có k là giá trị đầu tiên của trans­ver­sal, dựa vào giá trị từng phần tử phía sau, ta thay bằng k. Chọn ra M trans­ver­sal của k=0,1,,M1 ta sẽ tạo thành 1 or­thog­o­nal ma­trix.

Thuật toán đếm số trans­ver­sal của một ma trận vuông N.

typedef vector<vector<int>> mat;
class transversal {
private:
    int n;
    vector<bool> row;
    vector<bool> nums;
    mat input;
    int cnt;
public:
    transversal(const mat& in_): n(in_.size()), input(in_),
    row(n, false), nums(n, false), cnt(0) {
        assert(in_.size() == in_[0].size());
    }

    void print() { // debugging
        for (auto& rows: input) {
            for (auto& e: rows) cout << e << " ";
            cout << endl;
        }
    }

    int compute(int idx) {
        cnt = 0;
        fill(row.begin(), row.end(), false);
        fill(nums.begin(), nums.end(), false);
        nums[input[idx][0]] = true;
        row[idx] = true;
        comp(1);
        return cnt;
    }


private:
    void comp(int idx) { //idx = index of column
        if (idx == n) {
            cnt++;
            return;
        }

        for (int i = 0; i < n; ++i) {
            if (!row[i] && !nums[input[i][idx]] ) {
                row[i] = true;
                nums[input[i][idx]] = true;
                comp(idx+1);
                row[i] = false;
                nums[input[i][idx]] = false;
            }
        }

    }
};

Lấy in­put từ cuốn TAoCP ra được kết quả:

79 96 76 87 70 84 83 75 95 63

Bem, đúng với kết quả trong sách. Chỉnh sửa một chút ta có thể có được toàn bộ các trans­ver­sal cần tìm. Sau đó với mỗi trans­ver­sal của k, ta chọn ra bộ nào không có phần tử trùng (trong in­dex của or­thog­o­nal ma­trix), ta sẽ tìm được ma trận. Chi tiết về phần này sẽ được đề cập trong bài viết nói về Danc­ing Links.

Tài liệu tham khảo

  1. Knuth, Donald E. The Art of Computer Programming, Volume 4A: Combinatorial Algorithms, Part 1.
  2. Latin square - Wikipedia
  3. http://​elscor­cho.50webs.com/​latin­squares.pdf