N×N 棋盤,部分格子為牆 (X),在空格 (.) 放置碉堡,要求**同行同列無牆阻隔的兩碉堡不可相見**。求最大放置數。本質是**一般圖的最大獨立集** (NP-Hard),但 N≤4 且牆壁將棋盤分割為小區域,回溯可行。
牆壁將行/列分割成段。同一段內最多放 1 個碉堡。可視為:每個空格屬於一個「行段」和一個「列段」,選擇格子等價於選擇行段-列段配對,**雙圖最大匹配**模型。但 N 小,直接 DFS 回溯更簡單。
row[i]:第 i 行當前段是否已放碉堡col[j]:第 j 列當前段是否已放碉堡used[i][j]:當前格子是否在本路徑被佔用void dfs(int cnt) {
ans = max(ans, cnt);
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j] == '.' && !used[i][j] && !row[i] && !col[j]) {
used[i][j] = row[i] = col[j] = 1;
dfs(cnt + 1);
used[i][j] = row[i] = col[j] = 0;
}
else if (board[i][j] == 'X') { // 關鍵:牆壁阻隔,重置行列段狀態
row[i] = col[j] = 0;
}
}
}
}
**牆壁重置** 是核心:同一行遇到牆,之前段的佔用狀態失效,新段重新計算。
import java.util.Scanner;
public class HDU1045 {
static char[][] board;
static boolean[][] used;
static int[] row, col;
static int n, ans;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
n = sc.nextInt();
if (n == 0) break;
board = new char[n][n];
used = new boolean[n][n];
row = new int[n];
col = new int[n];
ans = 0;
for (int i = 0; i < n; i++) {
String s = sc.next();
for (int j = 0; j < n; j++) board[i][j] = s.charAt(j);
}
dfs(0);
System.out.println(ans);
}
}
static void dfs(int cnt) {
if (cnt > ans) ans = cnt;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j] == '.' && !used[i][j] && row[i] == 0 && col[j] == 0) {
used[i][j] = true; row[i] = 1; col[j] = 1;
dfs(cnt + 1);
used[i][j] = false; row[i] = 0; col[j] = 0;
} else if (board[i][j] == 'X') {
row[i] = 0; col[j] = 0; // 牆壁重置
}
}
}
}
}
HDU 1045 看似 NP-Hard,實則利用 **N≤4 且牆壁分割** 特性,簡單 DFS 回溯 + **牆壁重置行列狀態** 即可 AC。理解「行段/列段」概念,能舉一反三解決「放國王」、「放車」、「電路佈線」等棋盤約束問題。進階學習 **二分圖最大匹配**,將棋盤問題統一建模為圖論模型。