Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Python

นับวิธีเลือก Empty Cells N ช่องใน N×N Matrix (ทุก Row/Col ต้องมีอย่างน้อย 1) ใน Python

ปัญหา: N×N Binary Matrix (0=Empty, 1=Blocked) เลือก N Empty Cells ทำให้ทุก Row และทุก Column มี Chosen Cell อย่างน้อย 1 ตัว หาจำนวนวิธี Mod 10⁹+7

Observation: เลือก N Cells, N Rows, N Cols → ต้องเลือก **พอดี 1 Cell ต่อ Row และ 1 Cell ต่อ Column** = **Permutation Matrix** บน Empty Cells

อัลกอริทึม: Backtracking + Bitmask

  1. Row-by-Row: พยายามวาง Queen (Chosen Cell) ในแต่ละ Row
  2. Bitmask `bs`: Track Columns ที่ใช้แล้ว (Bit i = 1 → Col i ใช้แล้ว)
  3. Base Case: `i == n` → Found Valid Configuration (Return 1)
  4. Recursive: ลองทุก Column `j` ที่ `matrix[i][j]==0` และ `bs` ยังไม่มี Bit j

การติดตั้ง


class Solution:
    def solve(self, matrix):
        n = len(matrix)
        MOD = 10**9 + 7
        
        def dfs(row, used_cols_mask):
            if row == n:
                return 1
            
            total = 0
            for col in range(n):
                # Check: Empty Cell และ Column ยังไม่ถูกใช้
                if matrix[row][col] == 0 and not (used_cols_mask & (1 << col)):
                    total = (total + dfs(row + 1, used_cols_mask | (1 << col))) % MOD
            return total
        
        return dfs(0, 0)

# Test
ob = Solution()
matrix = [
    [0, 0, 0],
    [0, 0, 0],
    [0, 1, 0]
]
print(ob.solve(matrix))  # 4

คำอธิบาย

  • DFS Row 0 → n-1

  • Bitmask `used_cols_mask`: Bit i = 1 หมายถึง Column i ถูกเลือกแล้ว

  • `(1 << col)`: Bitmask ของ Column `col`

  • `used_cols_mask & (1 << col) == 0`: Column `col` ว่าง

  • `used_cols_mask | (1 << col)`: ตั้ง Bit `col` ให้เป็น 1

ตัวอย่าง Matrix 3×3


Row 0: [0, 0, 0]  → เลือก Col 0, 1, หรือ 2 ได้
Row 1: [0, 0, 0]  → เลือก Col ที่เหลือได้
Row 2: [0, 1, 0]  → Col 1 Blocked, เลือก Col 0 หรือ 2 ที่เหลือ

Valid Permutations (Column per Row):
[0, 1, 2] → Row2 Col2 Blocked ✗
[0, 2, 1] → Row2 Col1 Blocked ✗
[1, 0, 2] → Row2 Col2 OK ✓
[1, 2, 0] → Row2 Col0 OK ✓
[2, 0, 1] → Row2 Col1 Blocked ✗
[2, 1, 0] → Row2 Col0 OK ✓
[0, 2, ?] ... (Total 4 Valid)

Complexity

  • Time: O(n!) Worst Case (All Empty) → Practical สำหรับ n ≤ 10-12
  • Space: O(n) Recursion Stack
  • Optimization: DP + Bitmask (Held-Karp) O(n × 2ⁿ) สำหรับ n ≤ 20

DP + Bitmask Version (เร็วกว่าสำหรับ n ≤ 20)


def solve_dp(matrix):
    n = len(matrix)
    MOD = 10**9 + 7
    
    # dp[mask] = ways to fill first popcount(mask) rows with columns in mask
    dp = [0] * (1 << n)
    dp[0] = 1
    
    for mask in range(1 << n):
        row = bin(mask).count('1')  # Next row to fill
        if row >= n:
            continue
        for col in range(n):
            if matrix[row][col] == 0 and not (mask & (1 << col)):
                new_mask = mask | (1 << col)
                dp[new_mask] = (dp[new_mask] + dp[mask]) % MOD
    
    return dp[(1 << n) - 1]

Note: ปัญหานี้คือ **Counting Perfect Matchings in Bipartite Graph** (Rows ↔ Columns, Edge = Empty Cell) = **Permanent of 0-1 Matrix** (Permanent คล้าย Determinant แต่ไม่มี Sign)