首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

HDU 1045 Fire Net 炮台放置問題:DFS 回溯 + 剪枝求最大獨立集

     編輯:關於C

題目大意

給定 n × n(n ≤ 4)棋盤,格子為 `.`(空地)或 `X`(牆)。在空地放置炮台,規則:同一行/列無牆阻隔的兩個炮台不能互相攻擊(即同一行/列段內最多一個炮台)。求最大放置數。

核心模型:二分圖最大獨立集 / 最大匹配

將每個連續的行段、列段視為二分圖兩側頂點,空地為邊。本題 n ≤ 4 極小,**DFS 回溯暴力搜尋** 即可輕鬆通過,無需建圖跑匈牙利演算法。

DFS 回溯策略

  • 按行優先、列次之掃描棋盤。
  • 對每個空地 `.`,嘗試**放置**或**不放置**。
  • 放置條件:該行段、列段尚無炮台(用 `row[i]`、`col[j]` 標記)。
  • 遇到牆 `X` 時,重置對應行/列段標記(新段開始)。
  • 全局變數 `maxCnt` 記錄最大值,剪枝:剩餘空地數 + 當前數 ≤ `maxCnt` 直接返回。

完整 Java 代碼(AC 版)

import java.util.Scanner;

public class HDU1045 {
    static char[][] board;
    static boolean[][] used; // 當前搜尋路徑中該格是否已放炮台
    static int[] rowUsed, colUsed; // 當前行段/列段是否已有炮台
    static int n, maxCnt;

    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];
            for (int i = 0; i < n; i++) {
                String line = sc.next();
                for (int j = 0; j < n; j++) board[i][j] = line.charAt(j);
            }

            used = new boolean[n][n];
            rowUsed = new int[n];
            colUsed = new int[n];
            maxCnt = 0;

            dfs(0, 0, 0); // 從 (0,0) 開始,當前放置數 0
            System.out.println(maxCnt);
        }
    }

    // 線性索引 DFS:pos = i*n + j
    static void dfs(int pos, int cnt) {
        if (pos == n * n) { // 掃描完畢
            if (cnt > maxCnt) maxCnt = cnt;
            return;
        }

        int i = pos / n, j = pos % n;

        // 剪枝:剩餘格子數 + 當前數 ≤ maxCnt
        if (cnt + (n * n - pos) <= maxCnt) return;

        if (board[i][j] == '.' && rowUsed[i] == 0 && colUsed[j] == 0) {
            // 嘗試放置
            used[i][j] = true;
            rowUsed[i] = 1;
            colUsed[j] = 1;
            dfs(pos + 1, cnt + 1);
            // 回溯
            used[i][j] = false;
            rowUsed[i] = 0;
            colUsed[j] = 0;
        }

        // 不放置(或為牆/衝突)
        if (board[i][j] == 'X') {
            // 牆會自然分隔行/列段,但因我們按線性掃描,
            // 需在行/列段結束時重置。此處簡化:遇牆重置該行列標記。
            // 實際上,因掃描順序固定,牆後同行/列視為新段,
            // 只需在進入牆後第一個格子時 rowUsed/colUsed 已為 0(因上一格若為牆未置位,若為.已回溯歸零)。
            // 為嚴謹,顯式重置:
            rowUsed[i] = 0;
            colUsed[j] = 0;
        }
        dfs(pos + 1, cnt);
    }
}

關鍵點解析

技巧說明
線性索引 pos避免雙重迴圈回溯複雜度,單一遞歸參數控制進度
`rowUsed`/`colUsed` 區段標記遇牆歸零,模擬「牆阻隔視線」
剪枝 cnt + remaining ≤ maxCnt大幅減少搜尋樹節點,n=4 時幾乎瞬間出解
回溯三部曲標記 → 遞歸 → 復原

為何不需建圖跑最大匹配?

本題 n ≤ 4,棋盤最大 16 格,狀態空間極小(< 216 = 65536)。DFS 回溯加剪枝實際訪問節點數極少,代碼更簡短、易理解。若 n ≤ 100 則必須用二分圖最大匹配(匈牙利/網流)。

複雜度分析

  • 最壞情況(全 `.`):每格兩種選擇,O(2^(n²)),但剪枝極強。
  • 實際運行:n=4 時 < 1ms。
  • 空間:O(n²) 棋盤 + O(n) 標記陣列。

常見 WA 原因

  1. 未正確處理**牆分隔行/列段**(`rowUsed`/`colUsed` 重置邏輯)。
  2. 掃描順序導致段標記錯誤(建議線性索引 + 遇牆重置)。
  3. 剪枝條件寫錯(應為 `cnt + (n*n - pos) <= maxCnt`)。
  4. 多組測資未正確重置全域變數。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved