Hướng dẫn thuật toán

Trùm CUỐI 2026-01-11 11:22:15

Link bài tập

1. Phân tích bài toán

  • Đây là bài toán tìm số lượng ghép đôi hoàn hảo trên đồ thị hai phía.
  • Với N \le 21 , ta không thể dùng quay lui (độ phức tạp N! ) vì 21! là một số cực kỳ lớn.
  • Tuy nhiên, con số N=21 là dấu hiệu đặc trưng để sử dụng DP Bitmask với độ phức tạp khoảng O(2^N \cdot N) .

2. Ý tưởng thuật toán

  • Trạng thái DP: Gọi dp[mask] là số cách ghép cặp cho i người đàn ông đầu tiên với một tập hợp phụ nữ được biểu diễn bởi biến mask.
  • Giải thích Mask: mask là một số nguyên ở hệ nhị phân có N bit. Nếu bit thứ j của mask 1 , nghĩa là người phụ nữ thứ j đã được ghép cặp.
  • Mối liên hệ: Số lượng người đàn ông đã được ghép cặp luôn bằng số lượng bit 1 đang có trong mask. Ví dụ: nếu mask có 3 bit 1 , nghĩa là ta đang xét việc ghép cặp cho người đàn ông thứ 3 (hoặc chuẩn bị ghép cho người thứ 4, tùy cách bạn lập chỉ số).

3. Các bước thực hiện

  1. Khởi tạo:
    • Mảng dp có kích thước 2^N , tất cả bằng 0 .
    • Trạng thái cơ sở: dp[0] = 1 (chưa ghép ai là 1 cách).
  2. Duyệt qua các trạng thái:
    • Chạy vòng lặp mask từ 0 đến 2^N - 1 .
    • Với mỗi mask, xác định số lượng người đàn ông đã được ghép: i = số bit 1 trong mask.
    • Xét người đàn ông tiếp theo (người thứ i+1 ). Thử ghép ông ta với tất cả những người phụ nữ j mà:
      • Người phụ nữ j chưa bị chiếm trong mask (bit thứ j của mask đang là 0 ).
      • Người đàn ông i+1 và người phụ nữ j tương hợp ( a_{i,j} = 1 ).
    • Cập nhật trạng thái mới: dp[mask | (1 << j)] = (dp[mask | (1 << j)] + dp[mask]) % MOD.
  3. Kết quả:
    • Đáp án nằm ở dp[(1 << N) - 1] (tất cả các bit đều là 1 , nghĩa là tất cả mọi người đã được ghép cặp).

4. Lưu ý khi triển khai

  • Độ phức tạp: O(2^N \cdot N) . Với N=21 , số trạng thái là khoảng 2 triệu, nhân với N sẽ ra khoảng 42 triệu phép tính, hoàn toàn chạy kịp trong giới hạn thời gian (thường là 1-2 giây).
  • Xử lý bit:
    • Để đếm số bit 1 trong mask: Dùng hàm có sẵn như __builtin_popcount(mask) trong C++ hoặc bin(mask).count('1') trong Python.
    • Để kiểm tra bit thứ j có bật hay không: (mask >> j) & 1.
  • Bộ nhớ: Mảng dp với 2^{21} phần tử kiểu số nguyên (4 bytes) sẽ tốn khoảng 8MB, nằm an toàn trong giới hạn bộ nhớ thông thường (256MB).
  • Modulo: Đừng quên thực hiện phép chia lấy dư 10^9 + 7 ở mỗi bước cộng để tránh tràn số.